package com.sptci.echo2;

import java.io.Serializable;

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

import java.util.ArrayList;
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 nextapp.echo2.app.list.DefaultListModel;

import com.sptci.ReflectionUtility;

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

  /**
   * 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.
   */
  public ViewUpdater( Object uiContainer, Object bean )
  {
    super( uiContainer, bean );
  }

  /**
   * Update the fields of the {@link #uiContainer} with the data in
   * similarly named fields in {@link #bean}.
   *
   * @see ReflectionUtility#fetchField
   * @see #updateComponent
   * @throws BindingException If errors are encountered while
   *   accessing or setting the fields.
   */
  public void update()
  {
    try
    {
      for ( Map.Entry<String, Object> entry : beanValues().entrySet() )
      {
        Object object = 
          ReflectionUtility.fetchObject( entry.getKey(), uiContainer );
        updateComponent( object, entry.getValue() );
      }
    }
    catch ( Throwable t )
    {
      throw new BindingException( t );
    }
  }

  /**
   * Update the value of the specified code using the specified
   * value.
   *
   * @see #processMap
   * @see #updateSelectField
   * @param object The UI component that is to be updated.
   * @param value The value with which to update the object.
   * @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 void updateComponent( Object object, Object value )
    throws IllegalAccessException, InvocationTargetException
  {
    if ( object instanceof  nextapp.echo2.app.text.TextComponent )
    {
      Method method = ReflectionUtility.fetchMethod( object, 
          "setText", new Class[] { java.lang.String.class } );
      method.invoke( object, value );
    }
    else if ( object instanceof  java.util.Map )
    {
      processMap( (Map<String, ToggleButton>) object, 
          (Map<String, Boolean>) value );
    }
    else if ( object instanceof nextapp.echo2.app.SelectField )
    {
      updateSelectField( (SelectField) object, (List<ListItem>) value );
    }
    else if ( object instanceof nextapp.echo2.app.ListBox )
    {
      updateListBox( (ListBox) object, (List<ListItem>) value );
    }
  }

  /**
   * Update <code>ToggleButton</code> UI components.
   *
   * @param ui The map that contains the name and selected state
   *   of the toggle button.
   * @param value The new mappings to apply to the UI component.
   */
  protected void processMap( Map<String, ToggleButton> ui, 
      Map<String, Boolean> value )
  {
    for ( Map.Entry<String, ToggleButton> entry : ui.entrySet() )
    {
      String key = entry.getKey();

      if ( key != null && key.indexOf( ">" ) != -1 )
      {
        key = key.substring( key.indexOf( ">" ) + 1 );
      }
      
      if ( value.containsKey( key ) )
      {
        entry.getValue().setSelected( 
            ( (Boolean) value.get( key ) ).booleanValue() );
      }
      else 
      { 
        logger.info( "No key " + key + " found in map" );
      }
    }
  }

  /**
   * Update a <code>SelectField</code> UI component.
   *
   * @param selectField The UI component that is to be updated.
   * @param list The updated list data to apply to the UI component.
   */
  protected void updateSelectField( SelectField selectField,
      List<ListItem> list )
  {
    DefaultListModel model = new DefaultListModel();
    selectField.setSelectedIndex( 0 );
    int count = 0;
    for ( ListItem item : list )
    {
      model.add( count, item.getValue() );
      if ( item.isSelected() )
      {
        selectField.setSelectedIndex( count );
      }

      ++count;
    }

    selectField.setModel( model );
  }

  /**
   * Update a <code>ListBox</code> UI component.
   *
   * @param listBox The ListBox UI component that is to be updated.
   * @param list The updated list data to apply to the UI component.
   */
  protected void updateListBox( ListBox listBox, 
      List<ListItem> list )
  {
    DefaultListModel model = new DefaultListModel();
    int count = 0;
    ArrayList<Integer> indices = new ArrayList<Integer>();

    for ( ListItem item : list )
    {
      model.add( count, item.getValue() );
      if ( item.isSelected() )
      {
        indices.add( count );
      }
      ++count;
    }

    listBox.setModel( model );

    int[] selectedIndices = new int[ indices.size() ];
    count = 0;
    for ( int i : indices )
    {
      selectedIndices[ count++ ] = i;
    }

    listBox.setSelectedIndices( selectedIndices );
  }
}
