package com.sptci.echo2demo;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import nextapp.echo2.app.Alignment;
import nextapp.echo2.app.Button;
import nextapp.echo2.app.CheckBox;
import nextapp.echo2.app.Column;
import nextapp.echo2.app.Component;
import nextapp.echo2.app.ContentPane;
import nextapp.echo2.app.Extent;
import nextapp.echo2.app.Grid;
import nextapp.echo2.app.Insets;
import nextapp.echo2.app.Label;
import nextapp.echo2.app.ListBox;
import nextapp.echo2.app.Row;
import nextapp.echo2.app.SelectField;
import nextapp.echo2.app.TextArea;
import nextapp.echo2.app.TextField;
import nextapp.echo2.app.layout.GridLayoutData;
import nextapp.echo2.app.list.DefaultListModel;
import nextapp.echo2.app.text.TextComponent;

import nextapp.echo2.app.event.ActionEvent;
import nextapp.echo2.app.event.ActionListener;

import com.sptci.util.StringUtilities;
import com.sptci.echo2.JDOModelUpdater;
import com.sptci.echo2.JDOModelPropertyChangeListener;
import com.sptci.echo2.ListItem;
import com.sptci.echo2.ViewPropertyChangeListener;
import com.sptci.echo2.ViewUpdater;

/**
 * A <code>ContentPane</code> that builds the form used to input
 * user and job information.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-01-21
 * @version $Id: InputForm.java,v 1.10 2006/02/15 16:14:57 rakesh Exp $
 */
public class InputForm extends ContentPane implements ActionListener
{
  /**
   * The java bean used to store the values of the data for the
   * UI components.
   */
  private InputFormModel model;

  /**
   * The property change listener used to synchronise modifications
   * applied to UI components to the {@link #model} java bean.
   */
  private JDOModelPropertyChangeListener jdoChangeListener;

  /**
   * The property change listener used to synchronise modifications
   * applied to the {@link #model} java bean to the UI components.
   */
  private ViewPropertyChangeListener uiChangeListener;

  private TextField textField;
  private TextArea textArea;
  private HashMap<String, CheckBox> checkBoxes;
  private ListBox listBox;
  private SelectField selectField;

  private Button first;
  private Button previous;
  private Button last;
  private Button next;
  private Button save;
  private Button delete;
  private Label index;

  /**
   * Create a new instance of the form.
   *
   * @see #initForm
   */
  public InputForm()
  {
    this( new InputFormModel() );
  }

  /**
   * Create a new instance of the form using the specified java bean.
   *
   * @see #initForm
   * @param model The java bean to use to store the values of the
   *   UI components.
   */
  public InputForm( InputFormModel model )
  {
    this.model = model;
    /*
    try
    {
      jdoChangeListener = new JDOModelPropertyChangeListener( model );
    }
    catch ( java.beans.IntrospectionException iex )
    {
      throw new RuntimeException( iex.getMessage(), iex );
    }
    */

    initForm();

    //uiChangeListener = new ViewPropertyChangeListener( this );
    //this.model.addPropertyChangeListener( uiChangeListener );
  }

  /**
   * Initialise the UI components for the form.
   *
   * @see #createTextField
   * @see #createTextArea
   * @see #createCheckBoxes
   * @see #createSelectField
   * @see #createListBox
   * @see #createButton
   */
  private void initForm()
  {
    setStyleName( "InputForm.ContentPane" );
    Grid grid = new Grid( 3 );
    grid.setInsets( new Insets( 2, 2 ) );

    grid.add( createTextField( "textField" ) );
    grid.add( createTextArea( "textArea" ) );

    grid.add( createCheckBoxes() );
    grid.add( createSelectField() );
    grid.add( createListBox() );

    Row row1 = new Row();
    row1.add( createButton( "first" ) );
    row1.add( createButton( "previous" ) );

    Row row3 = new Row();
    row3.add( createButton( "next" ) );
    row3.add( createButton( "last" ) );

    Row row2 = new Row();
    row2.add( createButton( "save" ) );
    index = new Label();
    index.setToolTipText( "Record number" );
    row2.add( index );
    row2.add( createButton( "delete" ) );
    setIndexValue( InputFormModelFactory.getInstance().lastIndex() + 1 );

    grid.add( row1 );
    grid.add( row2 );
    grid.add( row3 );

    add( grid );
  }

  /**
   * Create the label and text field necessary to display the UI
   * components.
   *
   * @see #createLabel
   * @see #setText
   * @param name The name used to identify the text field to create.
   *   The name must match the name of the appropriate field
   *   without the Field suffix (eg. userName, salary, ...).  The
   *   same name must be configured in the configuration file for
   *   the properties and resources.
   * @return Component The new component that contains the newly
   *   created UI components.
   */
  private Component createTextField( String name )
  {
    Column column = new Column();
    column.setStyleName( "InputForm.LabelFieldColumn" );

    createLabel( name, column );

    TextField textField = new TextField();
    textField.setActionCommand( name );
    textField.setWidth( new Extent(
        Configuration.getInt( "InputForm." + name + ".width" ) ) );
    textField.setToolTipText( 
        Configuration.getString( "InputForm." + name + ".tooltip" ) );
    textField.setStyleName( "InputForm.TextComponent" );
    //textField.addPropertyChangeListener( jdoChangeListener );
    //setText( textField );

    try
    {
      Field field = getClass().getDeclaredField( name );
      field.set( this, textField );
    }
    catch ( Throwable t )
    {
      throw new RuntimeException( t.getMessage(), t );
    }

    column.add( textField );
    return column;
  }

  /**
   * Create a label using the specified name property and add it to the 
   * specified component.
   *
   * @param name The name used to identify the label to create.  The
   *   same name must be configured in the configuration file for
   *   the properties and resources.
   */
  private Label createLabel( String name, Component component )
  {
    Label label = new Label( 
        Configuration.getString( "InputForm." + name + ".label" ) );
    label.setToolTipText( 
        Configuration.getString( "InputForm." + name + ".tooltip" ) );
    label.setStyleName( "InputForm.Label" );
    component.add( label );

    return label;
  }

  /**
   * Set the text for the specified <code>TextComponent</code> from
   * the value in the {@link #model} data bean through {@link
   * #jdoChangeListener}.
   *
   * <p><b>Note:</b> It is mandatory that the <code>ActionCommand</code>
   * for the <code>TextComponent</code> be set prior to invoking this
   * method.</p>
   *
   * @param text The text component whose text is to be set.
   */
  private void setText( TextComponent text )
  {
    PropertyDescriptor descriptor = 
      jdoChangeListener.getProperties().get( text.getActionCommand() );
    if ( descriptor != null )
    {
      try
      {
        Object object = descriptor.getReadMethod().invoke( model );
        if ( object != null )
        {
          text.setText( object.toString() );
        }
      }
      catch ( Throwable t )
      {
        throw new RuntimeException( t.getMessage(), t );
      }
    }
  }

  /**
   * Create the label and text area necessary to display the UI
   * components.
   *
   * @see #createLabel
   * @see #setText
   * @param name The name used to identify the text area to create.
   *   The name must match the name of the appropriate area
   *   without the Area suffix (eg. userName, salary, ...).  The
   *   same name must be configured in the configuration file for
   *   the properties and resources.
   * @return Component The new component that contains the newly
   *   created UI components.
   */
  private Component createTextArea( String name )
  {
    Column column = new Column();
    GridLayoutData layout = new GridLayoutData();
    layout.setColumnSpan( 2 );
    column.setLayoutData( layout );
    createLabel( name, column );

    TextArea textArea = new TextArea();
    textArea.setActionCommand( name );
    textArea.setHeight( new Extent( Configuration.getInt( 
            "InputForm." + name + ".height" ), Extent.EM ) );
    textArea.setWidth( new Extent(
        Configuration.getInt( "InputForm." + name + ".width" ) ) );
    textArea.setToolTipText( 
        Configuration.getString( "InputForm." + name + ".tooltip" ) );
    textArea.setStyleName( "InputForm.TextComponent" );
    //textArea.addPropertyChangeListener( jdoChangeListener );
    //setText( textArea );

    try
    {
      Field field = getClass().getDeclaredField( name );
      field.set( this, textArea );
    }
    catch ( Throwable t )
    {
      throw new RuntimeException( t.getMessage(), t );
    }

    column.add( textArea );
    return column;
  }

  /**
   * Create the column necessary to display the check boxes and also
   * create the required check boxes.
   */
  private Component createCheckBoxes()
  {
    checkBoxes = new HashMap<String, CheckBox>();
    Column column = new Column();

    for ( Map.Entry<String, Boolean> entry : model.getCheckBoxes().entrySet() )
    {
      CheckBox checkBox = new CheckBox( entry.getKey() );
      checkBox.setActionCommand( "checkBoxes>" + entry.getKey() );
      checkBox.setStyleName( "InputForm.TextComponent" );
      checkBox.setSelected( entry.getValue() );
      //checkBox.addPropertyChangeListener( jdoChangeListener );
      column.add( checkBox );

      checkBoxes.put( checkBox.getActionCommand(), checkBox );
    }

    return column;
  }

  /**
   * Create the select field component.
   */
  private Component createSelectField()
  {
    selectField = new SelectField();

    DefaultListModel listModel = new DefaultListModel();
    int count = 0;
    for ( ListItem item : model.getSelectField() )
    {
      listModel.add( count++, item.getValue() );
      if ( item.isSelected() )
      {
        selectField.setSelectedIndex( count );
      }
    }
    selectField.setModel( listModel );
    selectField.setSelectedIndex( 0 );

    selectField.setActionCommand( "selectField" );
    //selectField.addPropertyChangeListener( jdoChangeListener );
    return selectField;
  }

  /**
   * Create the list box component.
   */
  private Component createListBox()
  {
    listBox = new ListBox();

    DefaultListModel listModel = new DefaultListModel();
    int count = 0;
    for ( ListItem item : model.getListBox() )
    {
      listModel.add( count++, item.getValue() );
    }
    listBox.setModel( listModel );

    listBox.setActionCommand( "listBox" );
    //listBox.addPropertyChangeListener( jdoChangeListener );
    return listBox;
  }

  /**
   * Create the button identified by the specified name.
   * 
   * @param name The name used to identify the button to create.
   *   The name must match the name of the appropriate button.  The
   *   same name must be configured in the configuration file for
   *   the properties and resources.
   * @return Component The new component that contains the newly
   *   created UI components.
   */
  private Button createButton( String name )
  {
    Button button = new Button( Configuration.getString( 
          "InputForm." + name + ".text" ) );
    button.setStyleName( "InputForm.Button" );
    button.setToolTipText( Configuration.getString( 
          "InputForm." + name + ".tooltip" ) );
    button.setActionCommand( name );
    button.addActionListener( this );

    try
    {
      Field field = getClass().getDeclaredField( name );
      field.set( this, button );
    }
    catch ( Throwable t )
    {
      throw new RuntimeException( t.getMessage(), t );
    }

    return button;
  }

  /**
   * Set the text of the {@link #index} label.  Enable or disable
   * the navigation buttons depending upon the values.
   *
   * @param index The index value to set.
   */
  private void setIndexValue( int index )
  {
    first.setEnabled( true );
    previous.setEnabled( true );
    next.setEnabled( true );
    last.setEnabled( true );
    delete.setEnabled( true );

    if ( index == 0 )
    {
      first.setEnabled( false );
      previous.setEnabled( false );
    }
    if ( index >= InputFormModelFactory.getInstance().lastIndex() )
    {
      last.setEnabled( false );
    }
    if ( index > InputFormModelFactory.getInstance().lastIndex() )
    {
      next.setEnabled( false );
      delete.setEnabled( false );
    }

    this.index.setText( String.valueOf( index ) );
  }
  
  /**
   * Returns {@link #model}.
   *
   * @return InputFormModel The value/reference of/to model.
   */
  public final InputFormModel getFormData()
  {
    return model;
  }
  
  /**
   * Set {@link #model}.
   *
   * @param model The value to set.
   */
  final void setModel( InputFormModel model )
  {
    //this.model.removePropertyChangeListener( uiChangeListener );
    this.model = model;
    //this.model.addPropertyChangeListener( uiChangeListener );
    /*
    try
    {
      jdoChangeListener.setBean( model );
    }
    catch ( Throwable t )
    {
      throw new RuntimeException( t.getMessage(), t );
    }
    */

    ( new ViewUpdater( this, model ) ).update();
  }
  
  /**
   * Returns {@link #jdoChangeListener}.
   *
   * @return JDOModelPropertyChangeListener The value/reference of/to 
   *   jdoChangeListener.
   */
  public final JDOModelPropertyChangeListener getDataChangeListener()
  {
    return jdoChangeListener;
  }

  /**
   * ActionListener implementation.  Actions are triggered when a
   * button is pressed.
   *
   * @param event The event that contains the information
   *   pertaining to the action.
   */
  public void actionPerformed( ActionEvent event )
  {
    if ( event.getActionCommand().equals( "save" ) )
    {
      doSave();
    }
    else if ( event.getActionCommand().equals( "delete" ) )
    {
      doDelete();
    }
    else if ( event.getActionCommand().equals( "first" ) )
    {
      doNavigate( 0 );
    }
    else if ( event.getActionCommand().equals( "previous" ) )
    {
      doNavigate( Integer.parseInt( index.getText() ) - 1 );
    }
    else if ( event.getActionCommand().equals( "next" ) )
    {
      doNavigate( Integer.parseInt( index.getText() ) + 1 );
    }
    else if ( event.getActionCommand().equals( "last" ) )
    {
      doNavigate( InputFormModelFactory.getInstance().lastIndex() );
    }
  }

  /**
   * The action for the {@link #save} button.
   */
  private void doSave()
  {
    save.setEnabled( false );
    save.setText( Configuration.getString( "InputForm.save.working" ) );
    InputFormModelFactory factory =
      InputFormModelFactory.getInstance();

    try
    {
      ( new JDOModelUpdater( this, model ) ).update();
      factory.save( model );
      setModel( new InputFormModel() );
      setIndexValue( factory.lastIndex() + 1 );
    }
    catch ( Throwable t )
    {
      ErrorPane pane = new ErrorPane( 
          t.getMessage(), StringUtilities.stackTrace( t ) );
      add( pane );
    }

    save.setEnabled( true );
    save.setText( Configuration.getString( "InputForm.save.text" ) );
  }

  /**
   * The action for the {@link #delete} button.
   */
  private void doDelete()
  {
    delete.setEnabled( false );
    delete.setText( Configuration.getString( "InputForm.delete.working" ) );
    InputFormModelFactory factory =
      InputFormModelFactory.getInstance();

    try
    {
      factory.delete( model );
      setModel( new InputFormModel() );
      setIndexValue( factory.lastIndex() + 1 );
    }
    catch ( Throwable t )
    {
      ErrorPane pane = new ErrorPane( 
          t.getMessage(), StringUtilities.stackTrace( t ) );
      add( pane );
    }

    delete.setEnabled( true );
    delete.setText( Configuration.getString( "InputForm.delete.text" ) );
  }

  /**
   * The action for the navigation buttons.
   *
   * @param index The index in a List of {@link InputFormModel} objects at
   *   which the object is to be retrieved.
   */
  private void doNavigate( int index )
  {
    InputFormModel data = 
      InputFormModelFactory.getInstance().fetchByIndex( index );
    if ( data == null )
    {
      data = new InputFormModel();
    }

    setModel( data );
    setIndexValue( index );
  }
}
