/*
 * ClientSideCusor.java    1.0 97/08/28
 *
 * Copyright (c) 1997 Netscape Communications Corporation
 *
 * Netscape grants you a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Netscape.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * See the CDK License Agreement for additional terms and conditions.
 * created: 2/97 cls
 */

package netscape.peas;


import netscape.palomar.sgml.*;
import netscape.palomar.htmllite.*;
import netscape.palomar.util.*;
import java.util.*;
import java.net.URL;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;

import java.io.ByteArrayOutputStream;

/**
 *  ClientSideCursor is a java class, which can be instantiated via
 *  the LiveConnect "new" operator, and can, in conjunction with a
 *  running LiveWire (server) application, process SQL Select statements.
 *
 *  It also supports the "Table" bean interfaces which allows it to
 *  be connected to other components supporting bean interfaces.
 *
 */
public class ClientSideCursor extends TableSupport {

    /**
     * default constructor
     */
    public ClientSideCursor() {
        super( true );
        setFetchBufferSize( 100 );
    }


    //---------------------------------------------------------------------
    // accessors


//http://alligator/cursor/cursor.html?SQL=select+%2a+from+customer
//http://alligator/cursor/cursor.html?SQL=

    public void setURL( String url) {
        _url = url;
    }

    public String getURL() {
        return _url;
    }

    public void setSQL(String sql) {
        _sql = sql;
        parseQueryStatement();
    }

    public String getSQL() {
        return _sql;
    }

    public String getRawText() {
        return _raw;
    }

    public SGMLTag getParsedHTML() {
        return _data;
    }

    static String encodeURL( String sURL ) {
        //return sURL.replace( ' ', '+' );
        return java.net.URLEncoder.encode( sURL );
    }


    /**
     * Serializes (to HTML text) given PageElement.
     */
    public static String toString( SGMLTag oElement1 ) {

        ByteArrayOutputStream aByteArrayStream = new ByteArrayOutputStream();
        DataOutputStream aOut = new DataOutputStream( aByteArrayStream );

        try {
            oElement1.writeYourself( aOut );
            return ( aByteArrayStream.toString() );

        } catch ( Exception ex ) {
            return null;
        }
    }

    public void fetch() {
		try {

		    //String request = _url + "?SQL=" + java.net.URLEncoder.encode(_sql);
		    //String request = _url + "?SQL=" + _sql;
		    String request = _url + queryCmdPrefix + encodeURL( _sql );
            System.out.println( "SQL Request : " + request );


		    //System.out.println("  Request = " + request);
		    URL purl = new URL(request);
		    //System.out.println("  got URL" );

		    //Object pcontent = purl.getContent();
		    InputStream oStream = purl.openStream();
		    Object pcontent = oStream;

		    //System.out.println("  got content" );

    		_data = null;
    		_raw = null;
		    if ( pcontent instanceof String ) {
		        //System.out.println("Got Text: " + pcontent.toString() );
    		    _data = (SGMLTag) LiteParser.getParser().generateTag("<BODY>");
    		    SGMLText ht = new SGMLTextImpl();
    		    ht.setUnicode(pcontent.toString());
    		    _data.addChildQuiet(ht);
    		} else if (pcontent instanceof InputStream) {
		        System.out.println("Got InputStream" + pcontent );

		        /*
		        {
		            InputStream oInputStream = oStream;
		            //oInputStream.reset();

                    //((InputStream)pcontent).mark(10000);
		            DataInputStream oDIS = new DataInputStream( oInputStream );
		            String sLine = null;
		            do {
		                sLine = oDIS.readLine();
		                System.out.println( " = " + sLine );
		            } while ( null != sLine );
		        }
                */




    		    _data = (SGMLTag) LiteParser.getParser().parseStream((
    		                (java.io.InputStream)pcontent));
    		                
                System.out.println( "after parseStream" );
                
                System.out.println( "===========================\r\n"+toString( _data )+"\r\n===========================" );
                
                
                
                
//    		    processHTML();
    		} else {
		        //System.out.println("Got Something else");
    		    _data = (SGMLTag) LiteParser.getParser().generateTag("<BODY>");
    		    SGMLText ht = new SGMLTextImpl();
    		    ht.setUnicode("Don't know how to handle " + pcontent.toString());
    		    _data.addChildQuiet(ht);
    		}
		} catch (Exception e) {
		    System.out.println("Got EXCEPTION! \n -- " + e.toString());
		}
    }

    SGMLTag filterGet(TreeEnumeration e, String tokenName) throws CascadedException {
        //System.out.println( "filterGet( TreeEnumeration " + e + ", String " + tokenName +");" );
        while (true) {
            if (!e.hasMoreElements())
                return null;

            //System.out.println( "a" );
            
            SGMLElement pe = (SGMLElement) e.nextElement();
            
            //System.out.println( "pe = " + pe );
            //System.out.println( " pe.getTagType() = " + pe.getTagType() );
            
            if (tokenName.equals(pe.getTagType()))
                return (SGMLTag) pe;
        }
    }

    SGMLTag filterGet(Enumeration e, String tokenName) throws CascadedException {
        while (true) {
            if (!e.hasMoreElements())
                return null;
            SGMLElement pe = (SGMLElement) e.nextElement();
            if (tokenName.equals(pe.getTagType()))
                return (SGMLTag) pe;
        }
    }

    String getUnderText(SGMLElement pe) throws CascadedException {
        Enumeration e = pe.getChildren();
        if (e.hasMoreElements()) {
            SGMLElement child = (SGMLElement) e.nextElement();
            if (child instanceof SGMLText) {
                return ((SGMLText)child).getUnicode();
            }
        }
        return "";
    }


    public void processHTML() throws CascadedException {
        //first find the TABLE object
        _tableElement = null;
        _columnNames = new Vector();
        _rowElements = new Vector();
        _columnDataTypes.clear();

        TreeEnumeration eDataTree = new TreeEnumeration(_data);

//System.out.println( "before first filterGet TABLE" );
        _tableElement = filterGet(eDataTree, "TABLE");
        
        if (null == _tableElement) {
            CSCException ce = new CSCException(CSCException.TABLE_TAG);
            throw ce;
        }
//System.out.println( "after filterGet" );
        

        //the first row should be the column names
        Enumeration eRows = _tableElement.getChildren();
        SGMLTag aRow = filterGet(eRows, "TR");
        if (null == aRow) {
            CSCException ce = new CSCException(CSCException.HEADER_ROW);
            throw ce;
        }
        Enumeration eCells = aRow.getChildren();
        SGMLTag aCell;
        while (null != (aCell = filterGet(eCells, "TH"))) {
            _columnNames.addElement(getUnderText(aCell));
//System.out.println( "Cell[Col Names] = " + getUnderText(aCell) );
        }

        //now get the data rows
        while (null != (aRow=filterGet(eRows, "TR"))) {
            Vector rowSet = new Vector();
            eCells = aRow.getChildren();
            while (null != (aCell = filterGet(eCells, "TD"))) {
                rowSet.addElement(getUnderText(aCell));
//System.out.println( "Cell[Col Values] = " + getUnderText(aCell) );
            }
            _rowElements.addElement(rowSet);
        }

        // Get column data type information
        SGMLTag  colTypeElement = filterGet(eDataTree, "TABLE");
        if (null == colTypeElement) {
            CSCException ce = new CSCException(CSCException.TABLE_TAG);
            throw ce;
        }
        eRows = colTypeElement.getChildren();
        while (null != (aRow = filterGet(eRows, "TR"))) {
            eCells = aRow.getChildren();
            aCell = filterGet(eCells, "TD");
            if (null == aCell) {
                CSCException ce = new CSCException(CSCException.TABLE_TAG);
                throw ce;
            }
            String colName = getUnderText(aCell).trim();
            aCell = filterGet(eCells, "TD");
            if (null == aCell) {
                CSCException ce = new CSCException(CSCException.TABLE_TAG);
                throw ce;
            }
            String colType = getUnderText(aCell).trim();
//System.out.println( "Col Type " + colName + " = " + colType );
            _columnDataTypes.put( colName, colType );
        }
    }

    public Enumeration getColumnNameEnumeration() {
        return _columnNames.elements();
    }
    public Enumeration getRowVectors() {
        return _rowElements.elements();
    }

    //
    //------------------------------------------------

    public void setQuery( String sNewQuery ) {
        setSQL( sNewQuery );
    }

    public void executeQuery() {
        try {

            fetch();
            processHTML();

            // set the column names...
            int iNumColumns = 0;
            if ( null != _columnNames ) {
                iNumColumns = _columnNames.size();
            }

            setNumColumns( iNumColumns );

            String aColumnNames[] = new String[iNumColumns];
            if ( iNumColumns > 0 ) {
                Enumeration eColumnNames = getColumnNameEnumeration();

                int i = 0;
                while ( eColumnNames.hasMoreElements() ) {
                    aColumnNames[i++] = (String)eColumnNames.nextElement();
                }

            }
            setColumnNames( aColumnNames, null, null );

            System.out.println( "CSC.executeQuery about to call superclass " );
            super.executeQuery();
            System.out.println( "CSC.executeQuery - " );
        } catch ( CascadedException ex ) {
            System.out.println( " Caught exception in executequery : " + ex );
        }
    }

    public void fetchMoreRows() {
        if ( true == getRowsPending() ) {
            int iStartRow = getNumRowsFetched();

            int iTotalNumRows = _rowElements.size();

            int iFinishRow = Math.min( iTotalNumRows, iStartRow+getFetchBufferSize()) - 1;

            int iNumCols = getNumColumns();
            Object[] aRowValues = new Object[iNumCols];
            for ( int iRow = iStartRow; iRow<=iFinishRow; iRow++ ) {

                Vector vRow = (Vector)_rowElements.elementAt( iRow );

                for ( int iCol = 0; iCol < iNumCols; iCol ++ ) {

                    Object oValue = vRow.elementAt( iCol );
                    aRowValues[iCol] = new String( (String)oValue );
                }
                // Adds values to our model.  We notify listeners after all values are added
                // using a "ROWS_INSERTED" event
                addRowValues( aRowValues, false );
            }
            setRowsPending( getNumRowsFetched() < iTotalNumRows );
            fireTableChange( TableChangeEvent.ROWS_INSERTED, iStartRow, 0, null, this, null );
        }

    }

    /**
     * Override TableSupport.updateCurrentRow to verify if
     * the cursor is updatable.
     */
    public void updateCurrentRow() {
        if ( isCursorUpdatable() ) {
            super.updateCurrentRow();
        }
    }

    /**
     *  Override TableProvider.doUpdate() interface method of
     *  TableSupport class.  doUpdate() sends an HTTP request
     *  to the server side Livewire application with a SQL
     *  UPDATE statement which updates the current row.
     */
    public void doUpdateRow( Object[] aColumnNames, Object[] aNewValues, Object[] aOldValues ) {
        if ( isNewRow(aOldValues) )
            _insertRow( aColumnNames, aNewValues );
        else
            _updateRow( aColumnNames, aNewValues, aOldValues );
    }

    private void _insertRow( Object[] aColumnNames, Object[] aNewValues ) {
        // Construct sql update statement
        int colCounts = aColumnNames.length;
        if ( colCounts <= 0 ) return;

        String sqlInsertStmt = ("INSERT INTO " + _queryTables + " (");
        for( int i=0; i<colCounts; i++ ) {
            if ( i > 0 ) {
                sqlInsertStmt += ",";
            }
            String colName = ((String)aColumnNames[i]).trim();
            sqlInsertStmt += (" " + colName);
        }
        sqlInsertStmt += " ) VALUES (";
        for( int i=0; i<colCounts; i++ ) {
            if ( i > 0 ) {
                sqlInsertStmt += ",";
            }
            String colName = ((String)aColumnNames[i]).trim();
            String oldVal = sqlValueEncode( aNewValues[i].toString(), colName );
            sqlInsertStmt += (" " + oldVal);
        }
        sqlInsertStmt += ")";

        // Fire an HTTP request to server side Livewire app
        try {
		    String request = _url + updateCmdPrefix + encodeURL( sqlInsertStmt );
            System.out.println( "Insert : " + request );
		    URL purl = new URL( request );
		    InputStream oStream = purl.openStream();
		    DataInputStream din = new DataInputStream( oStream );
//            String line = null;
//            while( (line=din.readLine()) != null ) {
//                System.out.println( line );
//            }
		}
		catch( Exception e ) {
		    System.out.println( "Client Side Cursor update failed : " + e );
		}
    }

    private void _updateRow( Object[] aColumnNames, Object[] aNewValues, Object[] aOldValues ) {
        // Construct sql update statement
        int colCounts = aColumnNames.length;
        if ( colCounts <= 0 ) return;

        String sqlUpdateStmt = ("UPDATE " + _queryTables + " SET");
        for( int i=0; i<colCounts; i++ ) {
            if ( i > 0 ) {
                sqlUpdateStmt += ",";
            }
            String colName = ((String)aColumnNames[i]).trim();
            String newVal = sqlValueEncode( aNewValues[i].toString().trim(), colName );
            sqlUpdateStmt += (" " + colName + "=" + newVal);
        }
        sqlUpdateStmt += " WHERE";
        for( int i=0; i<colCounts; i++ ) {
            if ( i > 0 ) {
                sqlUpdateStmt += " AND";
            }
            String colName = ((String)aColumnNames[i]).trim();
            String oldVal = sqlValueEncode( aOldValues[i].toString(), colName );
            sqlUpdateStmt += (" " + colName + "=" + oldVal);
        }

        // Fire an HTTP request to server side Livewire app
        try {
		    String request = _url + updateCmdPrefix + encodeURL( sqlUpdateStmt );
            System.out.println( "Update : " + request );
		    URL purl = new URL( request );
		    InputStream oStream = purl.openStream();
		    DataInputStream din = new DataInputStream( oStream );
//            String line = null;
//            while( (line=din.readLine()) != null ) {
//                System.out.println( line );
//            }
		}
		catch( Exception e ) {
		    System.out.println( "Client Side Cursor update failed : " + e );
		}
    }

    /**
     * Delete the specified row from the table.
     * Notify listeners
     *
     * we override this to do actual deletion from database
     * table on server, then call defalut implementation to
     * remove row from tableSupport's data structure, and
     * to update listeners.
     *
     */
	public void deleteRow( int iRowNumber, boolean bNotify ) {

	    Object[] oldValues = getOriginalColumnValues();
	    Object[] colNames = getColumnNames();

        // Construct sql update statement
        int colCounts = colNames.length;
        if ( colCounts <= 0 ) return;

        String sqlDeleteStmt = ("DELETE FROM " + _queryTables + " WHERE ");
        for( int i=0; i<colCounts; i++ ) {
            if ( i > 0 ) {
                sqlDeleteStmt += " AND";
            }
            String colName = ((String)colNames[i]).trim();
            String oldVal = sqlValueEncode( oldValues[i].toString(), colName );
            sqlDeleteStmt += (" " + colName + "=" + oldVal);
        }

        // Fire an HTTP request to server side Livewire app
        try {
		    String request = _url + updateCmdPrefix + encodeURL( sqlDeleteStmt );
              System.out.println( "Delete : " + request );
		    URL purl = new URL( request );
		    InputStream oStream = purl.openStream();
		    DataInputStream din = new DataInputStream( oStream );
//            String line = null;
//            while( (line=din.readLine()) != null ) {
//                System.out.println( line );
//            }
		}
		catch( Exception e ) {
		    System.out.println( "Client Side Cursor delete failed : " + e );
		}
		super.deleteRow( iRowNumber, bNotify );
    }



    public void addRow() {
        super.addRow();
    }

    private void parseQueryStatement() {
        _queryTables = null;
        _queryColumns = null;
        String sqlStmt = _sql.toLowerCase();
        int columnNameIndex = sqlStmt.indexOf( selectClause );
        if ( columnNameIndex != -1 ) {
            columnNameIndex += selectClause.length();
        }
        int tableNameIndex = sqlStmt.indexOf( fromClause );
        if ( (columnNameIndex != -1) && (tableNameIndex != -1) ) {
            _queryColumns = _sql.substring(columnNameIndex, tableNameIndex).trim();
            tableNameIndex += fromClause.length();
            _queryTables = _sql.substring(tableNameIndex).trim();
        }
    }

    private boolean isCursorUpdatable() {
        return( mbUpdatable && _queryTables.indexOf(",") == -1 );
    }

    private String sqlValueEncode( String val, String colName ) {
        String retVal = val;
        String colType = (String)_columnDataTypes.get( colName );
        if ( (colType != null) && (colType.equals("string")) ) {
            retVal = "'" + val + "'";
        }
        return retVal;
    }

    public void setUpdatable( boolean bNewValue ) {
        mbUpdatable = bNewValue;
    }

    public boolean getUpdatable() {
        return mbUpdatable;
    }


    //----------------------------------------------------

    //-------- statics---------------
    private static final String selectClause = "select ";
    private static final String fromClause = " from ";

    private static final String cursorAppName = "/_CSCursor.html";
    private static final String queryCmdPrefix = cursorAppName + "?SQL=";
    private static final String updateCmdPrefix = cursorAppName + "?update=";

    // ------- instance variables -----------

    private String _url;
    private String _sql;
    private String _raw;
    private SGMLTag _data;
    private SGMLTag _tableElement;
    private Vector _columnNames = new Vector();
    private Vector _rowElements = new Vector();
    private Hashtable _columnDataTypes = new Hashtable();

    private String _queryTables = null;
    private String _queryColumns = null;
    private boolean mbUpdatable = true;

}
