package com.sptci.echo2;

import java.io.Serializable;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import nextapp.echo2.app.ListBox;
import nextapp.echo2.app.SelectField;
import nextapp.echo2.app.button.ToggleButton;
import nextapp.echo2.app.list.ListModel;
import nextapp.echo2.app.text.TextComponent;

import com.sptci.ReflectionUtility;

/**
 * An abstract <code>updater</code> used to update UI containers or
 * JavaBean objects from one another.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-02-07
 * @version $Id: Updater.java,v 1.3 2006/02/09 21:27:31 rakesh Exp $
 */
public abstract class Updater implements Serializable
{
  /**
   * The logger used to log errors/warnings to.
   */
  private static transient final Logger logger =
    Logger.getLogger( "com.sptci.echo2.Updater" );

  /**
   * The UI container that is to be updated.
   */
  protected Object uiContainer;

  /**
   * The java bean that contains the data to use to update the
   * {@link #uiContainer}.
   */
  protected Object bean;

  /**
   * Default constructor.  Cannot be instantiated.
   */
  protected Updater() {}

  /**
   * Create a new instance with the specified UI container and java 
   * bean.
   *
   * @param uiContainer The {@link #uiContainer} to use.
   * @param bean The {@link #bean} to use.
   */
  protected Updater( Object uiContainer, Object bean )
  {
    setUiContainer( uiContainer );
    setBean( bean );
  }

  /**
   * Fetch the values of all the fields in {@link #bean} that have
   * an <code>accessor</code> method conforming to JavaBean naming
   * convention.
   *
   * @see ReflectionUtility#fetchFields
   * @see ReflectionUtility#fetchMethod( Object, String, Class[] )
   * @return Map A map with the field name as <code>key</code> and
   *   the field value as <code>value</code>.
   * @throws IllegalAccessException If a custom security policy
   *   restricts access to the bean fields.
   * @throws InvocationTargetException If the accessor method for
   *   the field could not be invoked successfully.
   */
  protected Map<String, Object> beanValues() 
    throws IllegalAccessException, InvocationTargetException
  {
    Map<String, Object> values = new HashMap<String, Object>();
    for ( Map.Entry<String, Field> entry : 
        ReflectionUtility.fetchFields( bean ).entrySet() )
    {
      Field field = entry.getValue();
      String methodName = "get" + 
        field.getName().substring( 0, 1 ).toUpperCase() + 
        field.getName().substring( 1 );
      Method method = ReflectionUtility.fetchMethod( bean,
          methodName, new Class[0] );
      if ( method != null )
      {
        Object value = method.invoke( bean );
        values.put( field.getName(), value );
      }
      else
      {
        logger.fine( "No accessor method in bean: " + bean.getClass() +
            " for field: " + field.getName() );
      }
    }

    return values;
  }

  /**
   * Fetch the values of all the fields in {@link #uiContainer}.
   * Convert the values of the UI components to the mapping scheme
   * used in bean generation.
   *
   * @see ReflectionUtility#fetchFields
   * @see ReflectionUtility#fetchMethod( Object, String, Class[] )
   * @return Map A map with the field name as <code>key</code> and
   *   the field value as <code>value</code>.
   * @throws IllegalAccessException If a custom security policy
   *   restricts access to the bean fields.
   * @throws InvocationTargetException If the accessor method for
   *   the field could not be invoked successfully.
   */
  protected Map<String, Object> uiValues() 
    throws IllegalAccessException, InvocationTargetException
  {
    Map<String, Object> values = new HashMap<String, Object>();

    for ( Map.Entry<String, Field> entry : 
        ReflectionUtility.fetchFields( uiContainer ).entrySet() )
    {
      Field field = entry.getValue();
      Object object = field.get( uiContainer );
      Object value = null;

      if ( object instanceof nextapp.echo2.app.text.TextComponent )
      {
        value = ( (TextComponent) object ).getText();
      }
      else if ( object instanceof nextapp.echo2.app.ListBox )
      {
        List<ListItem> list = new ArrayList<ListItem>();
        ListBox box = (ListBox) object;
        ListModel model = box.getModel();
        int[] selected = box.getSelectedIndices();
        HashSet<Integer> set = new HashSet<Integer>( selected.length );
        for ( int i : selected )
        {
          set.add( i );
        }

        for ( int i = 0; i < model.size(); ++i )
        {
          if ( set.contains( i ) )
          {
            list.add( new ListItem( (String) model.get( i ), true ) );
          }
          else
          {
            list.add( new ListItem( (String) model.get( i ), false ) );
          }
        }

        value = list;
      }
      else if ( object instanceof nextapp.echo2.app.SelectField )
      {
        List<ListItem> list = new ArrayList<ListItem>();
        SelectField select = (SelectField) object;
        ListModel model = select.getModel();
        int index = select.getSelectedIndex();

        for ( int i = 0; i < model.size(); ++i )
        {
          if ( i == index )
          {
            list.add( new ListItem( (String) model.get( i ), true ) );
          }
          else
          {
            list.add( new ListItem( (String) model.get( i ), false ) );
          }
        }

        value = list;
      }
      else if ( object instanceof java.util.Map )
      {
        Map<String, Boolean> map = new TreeMap<String, Boolean>();

        for ( Iterator iterator = ( (Map) object ).entrySet().iterator();
            iterator.hasNext(); )
        {
          Map.Entry mapEntry = (Map.Entry) iterator.next();
          if ( mapEntry.getValue() instanceof 
              nextapp.echo2.app.button.ToggleButton )
          {
            ToggleButton button = (ToggleButton) mapEntry.getValue();
            String key = (String) mapEntry.getKey();
            int index = 0;
            if ( ( index = key.lastIndexOf( ">" ) ) != -1 )
            {
              key = key.substring( index + 1 );
            }

            map.put( key, button.isSelected() );
          }
        }

        value = map;
      }

      values.put( entry.getKey(), value );
    }

    return values;
  }

  /**
   * Update the fields of the desired component with the data contained
   * in similarly named fields in the other component.
   */
  public abstract void update();
  
  /**
   * Returns {@link #uiContainer}.
   *
   * @return Object The value/reference of/to uiContainer.
   */
  public final Object getUiContainer()
  {
    return uiContainer;
  }
  
  /**
   * Set {@link #uiContainer}.
   *
   * @param uiContainer The value to set.
   */
  public final void setUiContainer( Object uiContainer )
  {
    this.uiContainer = uiContainer;
  }
  
  /**
   * Returns {@link #bean}.
   *
   * @return Object The value/reference of/to bean.
   */
  public final Object getBean()
  {
    return bean;
  }
  
  /**
   * Set {@link #bean}.
   *
   * @param bean The value to set.
   */
  public final void setBean( Object bean )
  {
    this.bean = bean;
  }
}
