package com.sptci.echo2;

import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyDescriptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import nextapp.echo2.app.ListBox;
import nextapp.echo2.app.SelectField;
import nextapp.echo2.app.button.ToggleButton;

import com.sptci.ReflectionUtility;

/**
 * An implementation of <code>PropertyChangeListener</code> used
 * to synchronise changes made to properties in UI components or the
 * {@link #bean} java bean with each other.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-01-22
 * @version $Id: PropertyChangeListener.java,v 1.4 2006/02/09 16:16:02 rakesh Exp $
 */
public abstract class PropertyChangeListener 
  extends com.sptci.PropertyChangeListener
{
  /**
   * The logger used to log errors to.
   */
  private static final Logger logger =
    Logger.getLogger( "com.sptci.echo2.PropertyChangeListener" );

  /**
   * Default constructor.  Just invokes super class constructor.
   */
  protected PropertyChangeListener() 
  {
    super();
  }

  /**
   * Create a new instance of the class using the specified java bean.
   *
   * @param bean The data object which is to be managed.
   * @throws IntrospectionException If errors are encountered while
   *   introspecting the {@link #bean} class.
   */
  public PropertyChangeListener( Object bean )
    throws IntrospectionException
  {
    super( bean );
  }

  /**
   * Over-ridden to handle special events generated by the Echo2 
   * framework.  Events that require special handling are usually
   * <code>CheckBox</code>, <code>AbstractList</code> and 
   * <code>RadioButton</code> components.  These components must
   * be modelled as <code>Map</code> and <code>List</code> fields in
   * the {@link #bean}.
   *
   * @see ReflectionUtility#fetchObject
   * @see #processMap
   * @see #processList
   * @param event A <code>PropertyChangeEvent</code> object that
   *   contains the property to change and the old and new values.
   * @param descriptor A <code>PropertyDescriptor</code> object that
   *   describes the property
   * @throws BindingException If errors are encountered while trying
   *   to invoke the appropriate method.
   */
  protected void modifyObject( PropertyChangeEvent event,
      PropertyDescriptor descriptor ) throws BindingException
  {
    try
    {
      Object object = 
        ReflectionUtility.fetchObject( descriptor.getName(), bean );

      if ( object instanceof java.util.Map )
      {
        processMap( (Map) object, event, descriptor );
      }
      else if ( object instanceof java.util.List )
      {
        processList( (List) object, event, descriptor );
      }
      else 
      {
        super.modifyObject( event, descriptor );
      }
    }
    catch ( Throwable t1 )
    {
      throw new BindingException( t1 );
    }
  }

  /**
   * Process a field with type <code>Map</code> in {@link #bean}.
   * Adds an entry to the map using the name and value in the
   * event.
   *
   * @param map The map that is to be updated.
   * @param event A <code>PropertyChangeEvent</code> object that
   *   contains the property to change and the old and new values.
   * @param descriptor A <code>PropertyDescriptor</code> object that
   *   describes the property
   */
  protected void processMap( Map map, PropertyChangeEvent event,
      PropertyDescriptor descriptor )
  {
    if ( event.getSource() instanceof 
        nextapp.echo2.app.button.ToggleButton )
    {
      ToggleButton button = (ToggleButton) event.getSource();
      String name = button.getActionCommand();

      if ( name != null && name.indexOf( ">" ) != -1 )
      {
        name = name.substring( name.indexOf( ">" ) + 1 );
      }
      map.put( name, event.getNewValue() );
    }
    else
    { 
      logger.warning( "Map error name: " + 
        descriptor.getName() + " object: " + map +
        " source: " + event.getSource() ); 
    }
  }

  /**
   * Process a field with type <code>List</code> in {@link #bean}.
   * Sets the value of the list entry at the appropriate index using
   * the values in the event.
   *
   * @param list The list that is to be updated.
   * @param event A <code>PropertyChangeEvent</code> object that
   *   contains the property to change and the old and new values.
   * @param descriptor A <code>PropertyDescriptor</code> object that
   *   describes the property
   */
  protected void processList( List list, PropertyChangeEvent event,
      PropertyDescriptor descriptor )
  {
    if ( event.getSource() instanceof 
        nextapp.echo2.app.ListBox )
    {
      ListBox listBox = (ListBox) event.getSource();
      for ( int index : listBox.getSelectedIndices() )
      {
        String value = (String) listBox.getModel().get( index );
        list.set( index, new ListItem( value, true ) );
      }
    }
    else if ( event.getSource() instanceof 
        nextapp.echo2.app.SelectField )
    {
      SelectField selectField = (SelectField) event.getSource();
      if ( selectField.getSelectedIndex() != -1 )
      {
        list.set( selectField.getSelectedIndex(),
            new ListItem( (String) selectField.getSelectedItem(), 
              true ) );
      }
    }
    else 
    { 
      logger.warning( "List error name: " + 
        descriptor.getName() + " object: " + list + 
        " source: " + event.getSource() ); 
    }
  }

  /**
   * Parse the field name specified, and remove any complex components
   * used to set the name as <code>actionCommands</code>.
   *
   * @param name The name that is to be parsed.
   * @return String The parsed and corrected (if necessary) name.
   */
  protected String parseName( String name )
  {
    if ( name != null && name.indexOf( ">" ) != -1 )
    {
      name = name.substring( 0, name.indexOf( ">" ) );
    }

    return name;
  }
}
