/*
 * RowSupport.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 java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;

/**
 * RowSupport provides a "simple" implementation to manage a row
 * of data.  A row of data consists of zero or more named fields.
 * These have values which can change, at which time registered
 * listeners will be notified via PropertyChange events.  A separate
 * event, RowChange is also available to notify interested RowChange
 * listeners of changes in the number of, names of, or other attributes
 * of the "columns" (fields). of this Row.
 */
public class RowSupport extends Object implements Row {

    /**
     * Construct a row support object.  oDelegator is another object
     * which delegates to this support object.
     *
     * bDelegatorOwnsDataContext flags whether or not the owner of
     * this object (and hence this object) "own" the "schema" information
     * of this row (e.g. names columns, etc.)
     */
    public RowSupport( Row oDelegator, boolean bDelegatorOwnsDataContext ) {
        moDelegator = oDelegator;
        if ( bDelegatorOwnsDataContext ) {
            claimDataContext( oDelegator );
        }
        moPropertyChangeSupport = new PropertyChangeSupport( oDelegator );
        moRowChangeSupport = new RowChangeSupport( moDelegator );
    }

    /**
     * Construct a row support object.  No delegator is specified, so
     * we delegat to ourselves.  For use by subclasses.
     */
    public RowSupport(  boolean bDelegatorOwnsDataContext ) {
        moDelegator = this;
        if ( bDelegatorOwnsDataContext ) {
            claimDataContext( moDelegator );
        }
        moPropertyChangeSupport = new PropertyChangeSupport( moDelegator );
        moRowChangeSupport = new RowChangeSupport( moDelegator );
    }


    //-----------------------------------------------------------------------------------------------------------
    // GetInterface interface

    /**
     * Return an object which implements this specified (fully qualified) interface name.
     * If this interface is not implemented, return null.
     */
    public Object getInterface( String sInterfaceName ) {
        if ( "netscape.peas.Row".equals( sInterfaceName ) ||
             "netscape.peas.RowChangeListener".equals( sInterfaceName ) ) {
            return getRowDelegator();
        } else {
            return null;
        }
    } // getInterface



    //----------------------------------------------------------------------------------------
    // DataContext negotiation

    /**
     * Attempt to claim DataContext.  Error condition??
     */
    private boolean claimDataContext( Row oOwner ) {
        if ( null == moDataContextOwner ) {
            if ( oOwner != moDataContextOwner ) {
                mbHasDataContext = true;
                moDataContextOwner = oOwner;
            }
            return true;
        }
        return false;
    } // claimDataContext

    /**
     * Set this row's column names, and notify listeners of the change in schema
     * by firing a RowChangeEvent.
     */
    public void setColumnNames( String[] aColumnNames, int[] aColumnWidths, NotifyList oNotify ) {
        synchronized ( this ) {
            maColumnWidths = aColumnWidths;
            maColumnNames = aColumnNames;
        }
        moDelegator.fireRowChange( RowChangeEvent.COLUMN_INFO_CHANGED, null, null, null, moDelegator, oNotify );
    }

    synchronized protected void resetOriginalValues() {
        maOriginalValues = getColumnValues();
        mbValuesChanged = false;
    }


    /**
     * Set this row's values with given array of values
     * notify our listeners also.
     *
     * Saved values are reset to these new values.  What this means (w.r.t. update)
     * is that calls to this method are interpreted as "we've got a new row",
     * as opposed to "the user changed all this row's values".  In order to
     * achieve the second, one ought to set each value individually using either
     * setValueByName or setValueByNumber.
     */
    public void setColumnValues( Object[] aValues, boolean bNotify ) {
        synchronized( this ) {
            maValues = aValues;
            resetOriginalValues();
            //maOriginalValues = aValues;
            //mbValuesChanged = false;
        }

        // fire property change events for each row number
        if ( bNotify ) {
            for ( int i=0; i<getNumColumns(); i++ ) {
                String sColumnName = getColumnName( i );
                Object oNewValue = getValueByNumber( i );
                moDelegator.firePropertyChange( sColumnName, null, getValueByNumber(i) );
                moDelegator.fireRowChange( RowChangeEvent.VALUE_CHANGED, sColumnName, null, oNewValue, getRowDelegator(), null );
            }
        }

    }

    public void setColumnName( int iCol, String sName ) {
        synchronized (this) {
            maColumnNames[iCol] = sName;
        }
    }

    public synchronized void setColumnWidth( int iCol, int iWidth ) {
        maColumnWidths[iCol] = iWidth;
    }


    public void setNumColumns( int iNumColumns ) {
        synchronized (this) {
            maColumnNames = new String[iNumColumns];
            maColumnWidths = new int[iNumColumns];
            maValues = new Object[iNumColumns];
            maOriginalValues = new Object[iNumColumns];
            mbValuesChanged = false;
            for ( int i=0; i < iNumColumns; i++ ) {
                maColumnNames[i] = new String( "" );
                maColumnWidths[i] = -1;
            }
            miNumColumns = iNumColumns;
        }

        // notify our listeners
    }


    //-----------------------------------------------------------------------------------------------------------
    // RowProvider interface

	/**
	 * Return the number of columns.
	 */
	public int getNumColumns() {
	    int iResult;
	    synchronized (this ) {
	        iResult = miNumColumns;
	    }
	    return iResult;
    }

    /**
     * Return an array of column names
     */
    public String[] getColumnNames() {
        String[] aResult = null;

        try {
            synchronized ( this ) {
                if ( null != maColumnNames ) {
                    aResult = (String[])maColumnNames.clone();
                }
            }
        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RS.getColumnNames: " + ex );
        }
        return aResult;
    }

    /**
     * Given a column's index (0..NumColumns), return its name
     */
    public String getColumnName( int iColumnNumber ) {
        if ( null != maColumnNames && isValidColumnNumber( iColumnNumber ) ) {
            synchronized ( this ) {
                return maColumnNames[iColumnNumber];
            }
        } else {
            return null;
        }
    }

    /**
     * Return an array of column widths
     */
    public int[] getColumnWidths() {

        int iNumColumns = getNumColumns();

        if ( iNumColumns >= 0 && null != maColumnWidths ) {
            int[] aResult = new int[iNumColumns];

             synchronized ( this ) {
                for ( int i = 0; i < iNumColumns; i++ ) {
                    aResult[i] = maColumnWidths[i];
                }
             }
             return aResult;

        }

        return null;
    }

    /**
     * Given a column's index (0..NumColumns), return its width
     */
    public int getColumnWidth( int iColumnNumber ) {
        if ( null != maColumnWidths && isValidColumnNumber( iColumnNumber ) ) {
            synchronized ( this ) {
                return maColumnWidths[iColumnNumber];
            }
        } else {
            return -1;
        }
    }


    /**
     * return values held by this row support object
     * clone here of in client??
     */
    public Object[] getColumnValues() {

        String[] aResult = null;

        synchronized ( this ) {
            int size = maValues.length;
            aResult = new String[size];
            for( int i=0; i<size; i++ ) {
                aResult[i] = new String((String)maValues[i]);
            }
        }

        return aResult;
    }

        /**
     * return values held by this row support object
     * clone here of in client??
     */
    public Object[] getOriginalColumnValues() {

        String[] aResult = null;

        synchronized ( this ) {
            int size = maOriginalValues.length;
            aResult = new String[size];
            for( int i=0; i<size; i++ ) {
                aResult[i] = new String((String)maOriginalValues[i]);
            }
        }

        return aResult;
    }


    /**
     * Given a column name, get its value.  This is a
     * "Dynamic-getter" method.  Unlike "normal" setter
     * methods ("void getFoo();"), dynamic-getters can be used
     * for properties whose names are known at compile time.
     */
    public Object getValueByName( String sColumnName ) {
        int iColumnNumber = getColumnNumber( sColumnName );
        return moDelegator.getValueByNumber( iColumnNumber );
    }

    /**
     * Another flavor of "Dynamic-getter" method.
     * See @getValueByName.
     */
	public Object getValueByNumber( int iColumnNumber ) {
	    if ( isValidColumnNumber( iColumnNumber ) && null != maValues ) {
    	    Object oResult = null;
    	    synchronized (this) {
    	        oResult = maValues[iColumnNumber];
    	    }
            return oResult;
	    } else {
    	    return null;
	    }
    }

    /**
     * Row data providers implement addPropertyChangeListener and publish
     * it in the beaninfo as their OnPropertyChange "add listner" method.
     *
     * Technically, it does not need to be a member of this interface, but
     * hey, why not?
     */
    public void addPropertyChangeListener( PropertyChangeListener x ) {
        moPropertyChangeSupport.addPropertyChangeListener( x );
    }

    /**
     * Your basic remove listener method
     */
    public void removePropertyChangeListener( PropertyChangeListener x ) {
       moPropertyChangeSupport.removePropertyChangeListener( x );
    }


    public void addRowChangeListener( RowChangeListener oRowReceiver ) {

        // push column values (and number of columns) over to RowReceiver object.
        // perhaps this ought to be more general, i.e. push an interface over there
        // and wait for a result.
        //String sColumnNames[] = {gsName,gsDescription,gsPrice};
        // receiver has opportunity to refuse connection here (e.g. another cursor
        // not willing to accept this object's DataContext..
        try {

            if ( mbHasDataContext ) {
                String aColumnNames[] = getColumnNames();
            }

            moRowChangeSupport.addRowChangeListener( oRowReceiver );
        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RowSupport.addRowChangeListener" );
        }
    }


    public void removeRowChangeListener( RowChangeListener oRowReceiver ) {
       moRowChangeSupport.removeRowChangeListener( oRowReceiver );
    }

    // end of RowProvider interface
    //-----------------------------------------------------------------------------------------------------------



    //------------------------------------------------------------------------
    // RowReceiver interface

    /**
     * Standard bean property change event. For us it means: "column value
     * in this row changed"
     */
    public void propertyChange( PropertyChangeEvent oEvent ) {
        moDelegator.setValueByName( oEvent.getPropertyName(), oEvent.getNewValue(), null );
    }

    public boolean isRowChanged() {
        return mbValuesChanged;
    }

    /**
     * Twister rowChange event. For us it means: "column info has changed".
     */
     public void rowChange( RowChangeEvent oEvent ) {
        try {

            switch ( oEvent.getType() ) {
                case RowChangeEvent.COLUMN_INFO_CHANGED: {
                    RowProvider oRowProvider = (RowProvider)oEvent.getRowProvider();
                    moDelegator.initializeColumnInfo( oRowProvider );
                    break;
                }
                case RowChangeEvent.VALUE_CHANGED: {
                    RowProvider oRowProvider = (RowProvider)oEvent.getRowProvider();
                    String sPropertyName = oEvent.getColumnAffected();
                    Object oNewValue = oEvent.getNewValue();
                    moDelegator.setValueByName( sPropertyName, oNewValue, oEvent.getNotifyList() );
                }

                default: {
                    break;
                }
            }

            // propegate the event
            moRowChangeSupport.propegateRowChange( oEvent );

        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RowSupport.rowChange" + ex );
        }

     } // rowChange


    /**
     * Given a column's name, set its value to be this new value.
     * This is a "dymanic-setter". See @RowReceiver.getValueByName
     */
    public void setValueByName( String sColumnName, Object oNewValue) {
        setValueByName( sColumnName, oNewValue, null );
    }
    public void setValueByName( String sColumnName, Object oNewValue, NotifyList oNotify ) {

        try {
            int iColumnNumber = getColumnNumber( sColumnName );
            moDelegator.setValueByNumber( iColumnNumber, oNewValue, oNotify );
        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RowSupport.setValueByName: " + ex );
        }
    }


    /**
     * Different flavor "dynamic-setter".  See @setValueByName.
     */
    public void setValueByNumber( int iColumnNumber, Object oNewValue, NotifyList oNotify ) {

        try {
            if ( isValidColumnNumber( iColumnNumber ) ) {
        	    synchronized (this) {

        	        if ( oNewValue instanceof String ) {
        	            maValues[iColumnNumber] = new String( (String)oNewValue );
        	        } else {
                        maValues[iColumnNumber] = oNewValue;
                    }
                    mbValuesChanged = true;
        	    }
            }

            // propegate event to all kinds of listeners
            firePropertyChange( getColumnName( iColumnNumber ), null, oNewValue );
            fireRowChange( RowChangeEvent.VALUE_CHANGED, getColumnName( iColumnNumber ), null, oNewValue, this, oNotify );

        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RowSupport.setValueByNumber: " + ex );
        }

    } // setValueByNumber


    /**
     * This method can be called by the data provider to
     * inform us (the client) of its column names
     */
    public boolean initializeColumnInfo( RowProvider oRowProvider ) {

        if ( mbHasDataContext ) {
            return false;
        } else {

            String[] aNames = oRowProvider.getColumnNames();
            int[] aWidths = oRowProvider.getColumnWidths();


            if ( null == aNames ) {
                miNumColumns = 0;
            } else {

                synchronized ( this ) {
                    // we get the length here also
                    miNumColumns = aNames.length;

                    // allocate model storage for column names, widths, values
                    maColumnNames = new String[miNumColumns];
                    maColumnWidths = new int[miNumColumns];
                    maValues = new Object[miNumColumns];

                    // "receive" the column labels... also tells us how many columns
                    for ( int i = 0; i < miNumColumns; i++ ) {
                        maColumnNames[i] = new String( aNames[i] );
                        maColumnWidths[i] = (null != aWidths) ? aWidths[i] : -1;
                        maValues[i] = null;
                    }
                }
            }
            return true;
        }

    }

    // end of RowDataReceiver interface
    //------------------------------------------------------------------------

    /**
     * support routine (also in javaform)
     */
    public int getColumnNumber( String sColumnName ) {
        if ( null != sColumnName && null != maColumnNames ) {
            for ( int i = 0; i < getNumColumns(); i++ ) {
                if ( sColumnName.equals( maColumnNames[i] ) ) {
                    return i;
                }
            }
        }
        return -1;

    } // getColumnNumber


    /**
     * Helper to validate a colun number.
     */
    public boolean isValidColumnNumber( int iColumnNumber ) {
        return ( 0 <= iColumnNumber && iColumnNumber < getNumColumns() );
    }

    /**
     * Delegate to our PropertyChangeSupport object.
     */
    public void firePropertyChange( String propertyName, Object oldValue, Object newValue ) {
        moPropertyChangeSupport.firePropertyChange( propertyName, oldValue, newValue );
    }


    /**
     * Delegate to our RowChangeSupport object.
     */
    public void fireRowChange( int iChangeType, String sColumnAffected,  Object oOldValue, Object oNewValue, RowProvider oRowProvider, NotifyList oNotify ) {
        try {
            moRowChangeSupport.fireRowChange( iChangeType, sColumnAffected, oOldValue, oNewValue, oRowProvider, oNotify );
        } catch ( Exception ex ) {
            System.out.println( "Caught exception in RowSupport.fireRowChange: " + ex );
        }
    }

    protected Row getRowDelegator() {
        return moDelegator;
    }

    protected boolean isNewRow( Object[] oOldValue ) {
        boolean ret = true;
        for ( int i=0; i<miNumColumns; i++ ) {
            if ( ((String)(oOldValue[i])).length() > 0 ) {
                ret = false;
                break;
            }
        }
        return( ret );
    }


    //------------------------------------------------------------
    // member variables

    private String[]    maColumnNames       = null;
    private int[]       maColumnWidths      = null;
    private Object      maValues[]          = null;
    private int         miNumColumns        = 0;
    private Object      maOriginalValues[]  = null;
    private boolean     mbValuesChanged     = false;
    /**
     * To this member, we delegate the following mthods:
     *     addPropertyChangeListener
     *     addPropertyChangeListener
     *     firePropertyChange
     */
    private PropertyChangeSupport moPropertyChangeSupport = null;
    private RowChangeSupport moRowChangeSupport = null;

    private boolean mbHasDataContext = false;
    private Row moDataContextOwner = null;
    private Row moDelegator       = null;

}
