/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

// Note 1 : comment "AMAVP" stands for "Accept methods as variable properties"
// stands for the BIG change when I first introduced
// expressions as possible values for the properties
package org.opensourcephysics.ejs.control;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.ejs.Simulation;
import org.opensourcephysics.ejs.control.value.BooleanValue;
import org.opensourcephysics.ejs.control.value.DoubleValue;
import org.opensourcephysics.ejs.control.value.ExpressionValue;
import org.opensourcephysics.ejs.control.value.IntegerValue;
import org.opensourcephysics.ejs.control.value.ObjectValue;
import org.opensourcephysics.ejs.control.value.StringValue;
import org.opensourcephysics.ejs.control.value.Value;

/**
 * <code>ControlElement</code> is a base class for an object that
 * can be managed using a series of configurable properties, hold actions
 * that when invoked graphically call other objects' methods, and be
 * responsible for the display and change of one or more internal variables.
 * <p>
 * <code>ControlElement</code>s can be included into a GroupControl,
 * thus acting in a coordinated way.
 * <p>
 * In fact, the best way to use a <code>ControlElement</code>, is to include
 * it into a GroupControl and then configure it using the
 * <code>setProperty()</code> method.
 * <p>
 * After this, the value common to several of these ControlElements can be
 * set and retrived using a single setValue() or getValue() call from the
 * ControlGroup.
 * <p>
 * You can also add any action you want to a ControlElement, but it is the
 * implementing class' responsability to trigger an action in response
 * to a user's gesture (with the mouse or keyboard)
 * <p>
 * @see     GroupControl
 */
public abstract class ControlElement {
  protected GroupControl myGroup = null;                                                   // The group of ControlElements with which I share variables
  protected Hashtable<String, String> myPropertiesTable = new Hashtable<String, String>(); // A place to hold any property
  protected Object myObject = null;                                                            // The wrapped object
  private boolean myActiveState = true;                                                        // Whether I am active or not
  private Vector<MethodWithOneParameter> myActionsList = new Vector<MethodWithOneParameter>(); // My list of actions
  private GroupVariable[] myProperties = null;                                                 // The variables for the registered properties
  private String[] myPropertiesNames = null;                                                   // The names of the registered properties
  protected boolean isUnderEjs = false;
  MethodWithOneParameter[] myMethodsForProperties = null;                                      // AMAVP
  ExpressionValue[] myExpressionsForProperties = null;                                         // AMAVP
  // ------------------------------------------------
  // Static constants and constructor
  // ------------------------------------------------
  public static final int NAME = 0;                                                            // The name of the element
  public static final int ACTION = 0;
  public static final int VARIABLE_CHANGED = 1;
  public static final int METHOD_FOR_VARIABLE = 2;                                             // AMAVP
  public static final String METHOD_TRIGGER = "_expr_";                                        // AMAVP //$NON-NLS-1$

  /**
   * Constructor ControlElement
   * @param _object
   */
  public ControlElement(Object _object) {
    // Create the list of registered properties
    ArrayList<?> info = getPropertyList();
    myObject = _object;
    myPropertiesNames = new String[info.size()];
    myProperties = new GroupVariable[info.size()];
    myMethodsForProperties = new MethodWithOneParameter[info.size()]; // AMAVP
    myExpressionsForProperties = new ExpressionValue[info.size()];    // AMAVP
    for(int i = 0; i<info.size(); i++) {
      String property = (String) info.get(i);
      myPropertiesNames[i] = property;
      myProperties[i] = null;
      myMethodsForProperties[i] = null; // AMAVP
      myExpressionsForProperties[i] = null;
    }
  }

  public Object getObject() {
    return myObject;
  }

  // ------------------------------------------------
  // Definition of Properties
  // ------------------------------------------------

  /**
   * Returns the list of all properties that can be set for this
   * ControlElement.
   * Subclasses that add properties should implement this.
   * Order is crucial here: Both for the presentation in an editor
   * (f.i. ViewElement) and for the setValue() method.
   */
  public abstract ArrayList<String> getPropertyList();

  /**
   * Returns information about a given property.
   * Subclasses that add properties should implement this.
   * Order in the implementation is irrelevant.
   * <ll>
   *   <li> The first keyword is ALWAYS the type. If more than one type is
   *     accepted, they are separated by | (do NOT use spaces!)
   *   <li> The keyword <b>CONSTANT</b> applies to properties that can not be
   *     changed using the setValue() methods
   *   <li> The keyword <b>VARIABLE_EXPECTED</b> is used when a String could be
   *     accepted, but a variable has priority. In this case, a String requires
   *     using inverted commas or quotes
   *   <li> The keyword <b>NotTrimmed</b> specifies that leading or trailing
   *     spaces must be respected when present. This is useful for labels or
   *     titles, for instance
   *   <li> The keyword <b>BASIC</b> is used by Ejs to group properties to the left
   *     hand side of the property editor
   *   <li> The keyword <b>HIDDEN</b> is used by Ejs so that it does not display
   *     an entry in the editor field
   *   <li> The keywords <b>PREVIOUS</b> and <b>POSTPROCESS</b> indicate that,
   *     when setting several properties at once (using setProperties()) the
   *     property must be process before, resp. after, the others
   *  </ll>
   */
  public abstract String getPropertyInfo(String _property);

  /**
   * Checks if a value can be considered a valid constant value for a property
   * If not, it returns null, meaning the value can be considered to be
   * a GroupVariable
   * @param     String _property The property name
   * @param     String _value The proposed value for the property
   */
  public Value parseConstant(String _propertyType, String _value) {
    if(_value==null) {
      return null;
    }
    Value constantValue;
    if(_propertyType.indexOf("boolean")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.booleanConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Color")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.colorConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("File")>=0) {       //$NON-NLS-1$
      String codebase = null;
      if(getProperty("_ejs_codebase")!=null) {   //$NON-NLS-1$
        codebase = getProperty("_ejs_codebase"); //$NON-NLS-1$
      } else if((getSimulation()!=null)&&(getSimulation().getCodebase()!=null)) {
        codebase = getSimulation().getCodebase().toString();
      }
      if(Utils.fileExists(codebase, _value)) {
        return new StringValue(_value);
      }
    }
    if(_propertyType.indexOf("Font")>=0) { //$NON-NLS-1$
      java.awt.Font currentFont = null;
      if(getVisual()!=null) {
        currentFont = getVisual().getFont();
      }
      constantValue = ConstantParser.fontConstant(currentFont, _value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Format")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.formatConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if((_propertyType.indexOf("Margins")>=0)||(_propertyType.indexOf("Rectangle")>=0)) { //$NON-NLS-1$ //$NON-NLS-2$
      constantValue = ConstantParser.rectangleConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    return null;
  }

  // ------------------------------------------------
  // Set and Get the values of the properties
  // ------------------------------------------------

  /**
   * Sets the value of the registered variables.
   * Subclasses with internal values should extend this
   * Order is crucial here: it must match exactly that of the getPropertyList()
   * method.
   * @param int _index   A keyword index that distinguishes among variables
   * @param Value _value The object holding the value for the variable.
   */
  public void setValue(int _index, Value _value) {
    switch(_index) {
       case NAME :
         if(myGroup!=null) {
           myGroup.rename(this, _value.toString());
         }
         break;
    }
  }

  public void setDefaultValue(int _index) {
    switch(_index) {
       case NAME :
         if(myGroup!=null) {
           myGroup.rename(this, null);
         }
         break;
    }
  }

  /**
   * Gets the value of any internal variable.
   * Subclasses with internal values should extend this
   * @param int _index   A keyword index that distinguishes among variables
   * @return Value _value The object holding the value for the variable.
   */
  public Value getValue(int _index) {
    return null;
  }

  // -------------------------------------------
  // Methods that deal with properties
  // -------------------------------------------

  /**
   * Sets a property for this <code>ControlElement</code>. Implementing
   * classes are responsible of deciding (by declaring them in the
   * getPropertyList() method) what properties turn into visual
   * changes, or different behaviour, of the ControlElement.
   * <p>
   * However, every propery is accepted, even if it is not meaningful for a
   * particular implementation of this interface. This can serve as a
   * repository of information for future use.
   * <p>
   * Implementing classes should make sure that the following
   * requirements are met:
   * <ll>
   *   <li> Properties can be set in any order. The final result
   *        should not depend on the order. Exceptions must be
   *        explicitly documented.
   *   <li> Any property can be modified. If so, the old value,
   *        and whatever meaning it had, is superseded by the
   *        new one. If the new one is null, the old one is simply removed
   *        and setDefaultValue(index) is called in case a precise default
   *        value should be used.
   *   <li> When the element is part of a GroupControl, final users should
   *        not use this setProperty method directly, but go through the
   *        corresponding method of the group.
   * </ll>
   * @return    This same element. This is useful to nest more
   *            than one call to <code>setProperty</code>
   * @param     String _property The property name
   * @param     String _value    The value desired for the property
   * @see       GroupControl
   */
  // This one is not final because a few of the subclasses
  // (f. i. ControlContainer and ControlTrace) need to overwrite it
  public ControlElement setProperty(String _property, String _value) {
    _property = _property.trim();
    if(_property.equals("_ejs_")) { //$NON-NLS-1$
      isUnderEjs = true;
    }
    // Let's see if the proposed property is registered as a real property
    int index = propertyIndex(_property);
    if(index<0) {
      // It is not a registered property. Store the value but do not call setValue()
      if(_value==null) {
        myPropertiesTable.remove(_property);
      } else {
        myPropertiesTable.put(_property, _value);
      }
      return this;
    }
    // The property is registered. Unregister and call setValue
    myMethodsForProperties[index] = null;     // AMAVP
    myExpressionsForProperties[index] = null; // AMAVP
    if(myProperties[index]!=null) { // remove from the list of listeners for this GroupVariable
      myProperties[index].removeElementListener(this, index);
      myProperties[index] = null;
    }
    if(_value==null) {                     // Treat the easy case separately, so that to avoid a lot of 'if (null)' checks
      if(myProperties[index]!=null) {      // remove from the list of listeners for this GroupVariable
        myProperties[index].removeElementListener(this, index);
        myProperties[index] = null;
      }
      setDefaultValue(index);              // use a default value
      myPropertiesTable.remove(_property); // remove the property
      return this;
    }
    // From now on, the value is, necessarily, not null
    // Some properties should not be trimmed ('text', for instance)
    if(!propertyIsTypeOf(_property, "NotTrimmed")) { //$NON-NLS-1$
      _value = _value.trim();
    }
    String originalValue = _value;
    // Because of backwards compatibility with version 3.01 or earlier
    // There might be confusion with constant strings versus variable names
    // This is the reason for most of the following block
    // From this version on, it is recommended that constant strings should be
    // delimited by either ' or "
    Value constantValue = null;
    if(_value.startsWith("%")&&_value.endsWith("%")&&(_value.length()>2)) {        //$NON-NLS-1$ //$NON-NLS-2$
      _value = _value.substring(1, _value.length()-1);                             // Force a variable or method
    } else if(_value.startsWith("@")&&_value.endsWith("@")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
      // empty                                                                                                     // Do nothing for parsed expressions
    } else if(_value.startsWith("#")&&_value.endsWith("#")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
      // empty                                                                                                     // Do nothing for variables such as f()
    } else {
      if(_value.startsWith("\"")||_value.startsWith("'")) { //$NON-NLS-1$ //$NON-NLS-2$
        // empty                                                                                                   // It IS a constant String, don't try anything else
      } else {
        // First look for a CONSTANT property that can not be associated to GroupVariables
        if(propertyIsTypeOf(_property, "CONSTANT")) {                                                       //$NON-NLS-1$
          constantValue = new StringValue(_value);
        }
        // Check for String properties
        if(constantValue==null) {
          if(propertyType(_property).equals("String")&&!propertyIsTypeOf(_property, "VARIABLE_EXPECTED")) { // See TextField f.i. //$NON-NLS-1$ //$NON-NLS-2$
            constantValue = new StringValue(_value);
          }
        }
      }
      //
      // End of the compatibility block
      //
      // Now try the particular parser
      // The particular parser comes first because it can discriminate between
      // a real String and a File, f.i.
      if(constantValue==null) {
        constantValue = parseConstant(propertyType(_property), _value);
      }
      // Finally the standard parser
      if(constantValue==null) {
        constantValue = Value.parseConstantOrArray(_value, true); // silentMode
      }
    }
    if(constantValue!=null) { // Just set the value for this property
      // System.out.println ("property = "+_property+" = "+_value+" of the element "+this.toString()+"  is a constant!");
      if((constantValue instanceof StringValue)&&propertyIsTypeOf(_property, "TRANSLATABLE") // Apply Translator //$NON-NLS-1$
        &&(OSPRuntime.getTranslator()!=null)) {    // added by D Brown 2007-10-17
        Object target = null;
        if(myGroup!=null) {
          target = myGroup.getTarget("_default_"); //$NON-NLS-1$
        }
        String translated = OSPRuntime.getTranslator().getProperty(target.getClass(), constantValue.getString());
        if(!constantValue.getString().equals(translated)) {
          constantValue = new StringValue(translated);
        }
      }
      setValue(index, constantValue);
    } else {                                       // Associate the property with a GroupVariable or Method for later use
      // System.out.println (_value+" for the property "+_property+" of the element "+this.toString()+"  is a variable!: "+originalValue);
      // if (myProperties[index]!=null) { // remove from the list of listeners for this GroupVariable
      // myProperties[index].removeElementListener(this,index);
      // myProperties[index] = null;
      // }
      if(myGroup!=null) {
        boolean isNormalVariable = true, isExpression = false;
        if(_value.startsWith("#")&&_value.endsWith("#")&&(_value.length()>2)) {                   //$NON-NLS-1$ //$NON-NLS-2$
          _value = _value.substring(1, _value.length()-1);
          isNormalVariable = true;
        } else if(_value.startsWith("@")&&_value.endsWith("@")&&(_value.length()>2)) {            //$NON-NLS-1$ //$NON-NLS-2$
          _value = _value.substring(1, _value.length()-1);
          originalValue = _value;
          isNormalVariable = false;
          isExpression = true;
        } else if(_value.indexOf('(')>=0) {
          isNormalVariable = false;                                                               // It mist be a method
        }
        // Begin --- AMAVP
        if(isNormalVariable) {                                                                    // Connect a variable property with a normal variable name
          // This is what would normally happen under Ejs with expressions
          Value newValue = null;
          // If not under Ejs, get the actual value and use it when you register
          // to the group. This is arguable...
          if(getProperty("_ejs_")==null) {                                                        //$NON-NLS-1$
            newValue = getValue(index);
          }
          if(newValue==null) {
            // if      (propertyIsTypeOf(_property,"[]"))      newValue = new ObjectValue(null);
            // else
            if(propertyIsTypeOf(_property, "double")) {                                           //$NON-NLS-1$
              newValue = new DoubleValue(0.0);
            } else if(propertyIsTypeOf(_property, "boolean")) {                                   //$NON-NLS-1$
              newValue = new BooleanValue(false);
            } else if(propertyIsTypeOf(_property, "int")) {                                       //$NON-NLS-1$
              newValue = new IntegerValue(0);
            } else if(propertyIsTypeOf(_property, "String")) {                                    //$NON-NLS-1$
              newValue = new StringValue(_value);
            } else {
              newValue = new ObjectValue(null);
            }
          }
          myProperties[index] = myGroup.registerVariable(_value, this, index, newValue);
        } else if(isExpression) {                                                                 // Connect a variable property to an expression
          String returnType = null;
          if(propertyIsTypeOf(_property, "double")) {                                             //$NON-NLS-1$
            returnType = "double";                                                                //$NON-NLS-1$
          } else if(propertyIsTypeOf(_property, "boolean")) {                                     //$NON-NLS-1$
            returnType = "boolean";                                                               //$NON-NLS-1$
          } else if(propertyIsTypeOf(_property, "int")) {                                         //$NON-NLS-1$
            returnType = "int";                                                                   //$NON-NLS-1$
          } else if(propertyIsTypeOf(_property, "String")) {                                      //$NON-NLS-1$
            returnType = "String";                                                                //$NON-NLS-1$
          } else if(propertyIsTypeOf(_property, "Action")) {                                      //$NON-NLS-1$
            returnType = "Action";                                                                //$NON-NLS-1$
          } else {
            System.out.println("Error for property "+_property+" of the element "+this.toString() //$NON-NLS-1$ //$NON-NLS-2$
                               +". Cannot be set to : "+originalValue); //$NON-NLS-1$
            myPropertiesTable.put(_property, originalValue);
            return this;
          }
          if(!returnType.equals("Action")) {                            //$NON-NLS-1$
            myExpressionsForProperties[index] = new ExpressionValue(_value, myGroup);
            myGroup.methodTriggerVariable.addElementListener(this, index);
            myProperties[index] = myGroup.methodTriggerVariable;
          }
        } else {                                                        // Connect a variable property to a method
          // System.out.println ("Connecting property "+_property+" to method '"+_value+"' for the element "+this.toString());
          // Under Ejs do something reasonable.
          // For instance Labels need a string to size themselves properly
          if(getProperty("_ejs_")!=null) {                                                          // Do nothing in Ejs //$NON-NLS-1$
            // System.out.println ("Under ejs");
          } else {
            String returnType = null;
            if(propertyIsTypeOf(_property, "double")) {                                             //$NON-NLS-1$
              returnType = "double";                                                                //$NON-NLS-1$
            } else if(propertyIsTypeOf(_property, "boolean")) {                                     //$NON-NLS-1$
              returnType = "boolean";                                                               //$NON-NLS-1$
            } else if(propertyIsTypeOf(_property, "int")) {                                         //$NON-NLS-1$
              returnType = "int";                                                                   //$NON-NLS-1$
              // else if (propertyIsTypeOf(_property,"byte"))    returnType = "byte";
            } else if(propertyIsTypeOf(_property, "String")) {                                      //$NON-NLS-1$
              returnType = "String";                                                                //$NON-NLS-1$
            } else {
              System.out.println("Error for property "+_property+" of the element "+this.toString() //$NON-NLS-1$ //$NON-NLS-2$
                                 +". Cannot be set to : "+originalValue);                                //$NON-NLS-1$
              myPropertiesTable.put(_property, originalValue);
              return this;
            }
            // Resolve for non-default target
            String[] parts = MethodWithOneParameter.splitMethodName(_value);
            if(parts==null) {
              System.err.println(getClass().getName()+" : Error! method <"+originalValue+"> not found"); //$NON-NLS-1$ //$NON-NLS-2$
              myPropertiesTable.put(_property, originalValue);
              return this;
            }
            if(parts[0]==null) {
              parts[0] = "_default_";                                                                  //$NON-NLS-1$
            }
            Object target = myGroup.getTarget(parts[0]);
            if(target==null) {
              System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
              myPropertiesTable.put(_property, originalValue);
              return this;
            }
            if(parts[2]==null) {
              _value = parts[1]+"()";                                                                                                //$NON-NLS-1$
            } else {
              _value = parts[1]+"("+parts[2]+")";                                                                                    //$NON-NLS-1$ //$NON-NLS-2$
            }
            myMethodsForProperties[index] = new MethodWithOneParameter(METHOD_FOR_VARIABLE, target, _value, returnType, null, this); // Pass the element itself Jan 31st 2004 Paco
            // Register the property of this element to a standard boolean (why not?) variable
            // myGroup.update() will take care of triggering the method
            myGroup.methodTriggerVariable.addElementListener(this, index);
            myProperties[index] = myGroup.methodTriggerVariable;
            // myProperties[index] = myGroup.registerVariable (METHOD_TRIGGER,this,index,new BooleanValue(false));
          } // End of the real part, i.e. not under Ejs
        }   // End --- AMAVP
      }
    }
    myPropertiesTable.put(_property, originalValue);
    return this;
  }

  /**
   * Sets more than one property at once. The pairs
   * <code>property=value</code> must be separated by ';'.
   * If any value has a ';' in it, then it must be set
   * in a separate <code>setProperty</code> call.
   * @return    This same element. This is useful to nest more
   *            than one call to <code>setProperties</code>
   * @param     String _propertyList The list of properties and Values
   *            to be set
   */
  final public ControlElement setProperties(String _propertyList) {
    Hashtable<String, String> propTable = new Hashtable<String, String>();
    StringTokenizer tkn = new StringTokenizer(_propertyList, ";"); //$NON-NLS-1$
    while(tkn.hasMoreTokens()) {
      String token = tkn.nextToken();
      if(token.trim().length()<=0) {
        continue;
      }
      int index = token.indexOf("=");                                                                   //$NON-NLS-1$
      if(index<0) {
        System.err.println(getClass().getName()+" : Error! Token <"+token+"> invalid for "+toString()); //$NON-NLS-1$ //$NON-NLS-2$
      } else {
        propTable.put(token.substring(0, index).trim(), token.substring(index+1));
      }
    }
    return setProperties(propTable);
  }

  // This is neccesary just to make sure that some properties are processed
  // first and some others (such as 'value') last
  private void preprocess(String _property, Hashtable<String, String> _propertyTable) {
    String value = _propertyTable.get(_property);
    if(value!=null) {
      setProperty(_property, value);
      _propertyTable.remove(_property);
    }
  }

  private ControlElement setProperties(Hashtable<String, String> _propertyTable) {
    // _ejs_ is used by Ejs to signal that the element is working under it.
    // This has some consequences in the behaviour of some properties
    // (f.i. 'exit' on a Frame will not exit the application)
    preprocess("_ejs_", _propertyTable); //$NON-NLS-1$
    Hashtable<String, String> postTable = new Hashtable<String, String>();
    for(Enumeration<String> e = _propertyTable.keys(); e.hasMoreElements(); ) {
      String key = e.nextElement();
      // Some need to be processed before the others
      if(propertyIsTypeOf(key, "PREVIOUS")) {           //$NON-NLS-1$
        preprocess(key, _propertyTable);
        // And some need to be the last ones
      } else if(propertyIsTypeOf(key, "POSTPROCESS")) { //$NON-NLS-1$
        String value = _propertyTable.get(key);
        _propertyTable.remove(key);
        postTable.put(key, value);
      }
    }
    // Process the normal ones
    for(Enumeration<String> e = _propertyTable.keys(); e.hasMoreElements(); ) {
      String key = e.nextElement();
      setProperty(key, _propertyTable.get(key));
    }
    // Finally proccess those which need to be the last ones
    for(Enumeration<String> e = postTable.keys(); e.hasMoreElements(); ) {
      String key = e.nextElement();
      setProperty(key, postTable.get(key));
    }
    return this;
  }

  /**
   * Returns the value of a property.
   * @param     String _property The property name
   */
  final public String getProperty(String _property) {
    return myPropertiesTable.get(_property);
  }

  /**
   * Returns wether a property information contains a given keyword in its preamble
   * @param     String _property The property name
   * @param     String _keyword The keyword to look for
   */
  final public boolean propertyIsTypeOf(String _property, String _keyword) {
    String info = getPropertyInfo(_property);
    if(info==null) {
      return false;
    }
    if(info.toLowerCase().indexOf(_keyword.toLowerCase())>=0) {
      return true;
    }
    return false;
  }

  /**
   * Returns the type of the property
   * @param     String _property The property name
   * @return    String The type of the property
   */
  final public String propertyType(String _property) {
    String info = getPropertyInfo(_property);
    if(info==null) {
      return "double"; //$NON-NLS-1$
    }
    StringTokenizer tkn = new StringTokenizer(info, " "); //$NON-NLS-1$
    if(tkn.countTokens()>=1) {
      return tkn.nextToken();
    }
    return "double"; //$NON-NLS-1$
  }

  /**
   * Provided for backwards compatibiliy only
   */
  public java.awt.Component getComponent() {
    return null;
  }

  /**
   * Provided for backwards compatibiliy only
   */
  public java.awt.Component getVisual() {
    return null;
  }

  /**
   * resets the element
   */
  public void reset() {}

  /**
   * initializes the element. A kind of soft reset()
   */
  public void initialize() {}

  /**
   * refresh the element
   */
  // final public void  update() { } Moved to interface NeedsUpdate

  /**
   * Returns the integer index of a given variable property
   */
  private int propertyIndex(String _property) {
    if(myPropertiesNames!=null) {
      for(int i = 0; i<myPropertiesNames.length; i++) {
        if(myPropertiesNames[i].equals(_property)) {
          return i;
        }
      }
    }
    return -1;
  }

  /**
   * Whether the element implements a given property
   * @param _property the property
   */
  public boolean implementsProperty(String _property) {
    return(propertyIndex(_property)>=0);
  }

  /**
   * Clear all registered internal variable properties
   */
  final public void variablePropertiesClear() {
    if(myPropertiesNames!=null) {
      for(int i = 0; i<myPropertiesNames.length; i++) {
        setProperty(myPropertiesNames[i], null);
      }
    }
  }

  /**
   *  Reports its  name, if it has been set. If not, returns
   *  a standard value.
   */
  public String toString() {
    String name = myPropertiesTable.get("name"); //$NON-NLS-1$
    if(name!=null) {
      return name;
    }
    String text = this.getClass().getName();
    int index = text.lastIndexOf("."); //$NON-NLS-1$
    if(index>=0) {
      text = text.substring(index+1);
    }
    return "Unnamed element of type "+text; //$NON-NLS-1$
  }

  /**
   * Clears any trace of myself (specially in the group)
   */
  public void destroy() {
    setProperty("parent", null); //$NON-NLS-1$
    if(myProperties!=null) {
      for(int i = 0; i<myProperties.length; i++) {
        if(myProperties[i]!=null) {
          myProperties[i].removeElementListener(this, i);
        }
      }
    }
  }

  // ------------------------------------------------
  // Actions
  // ------------------------------------------------

  /**
   * Defines a generic action that can be invoked from this
   * <code>ControlElement</code>. It is the responsability of implementing
   * classes to decide what actions types can be invoked and how.
   * <p>
   * If the method field is not a valid method for this target object
   * it will ignore the command (and perhaps print an error message).
   * <p>
   * @return    This same element. This is useful to nest it with
   *    other calls to <code>setProperty</code> or <code>adAction</code>.
   * @param     int _type      The action type
   * @param     Object _target The object whose method will be invoked
   * @param     String _method The method to call in the target object.
   * The method can accept a single CONSTANT parameter, either boolean, int,
   * double or String. See MethodWithOneParameter for more details.
   */
  final public ControlElement addAction(int _type, Object _target, String _method) {
    myActionsList.addElement(new MethodWithOneParameter(_type, _target, _method, null, null, this)); // null = void, null = no2nd action
    return this;
  }

  /**
   * This is an advanced form of addAction that allows for nested actions
   */
  final public ControlElement addAction(int _type, Object _target, String _method, MethodWithOneParameter _secondAction) {
    myActionsList.addElement(new MethodWithOneParameter(_type, _target, _method, null, _secondAction, this)); // null = void
    return this;
  }

  /**
   * Similar to the other addAction but extracts the target from the method,
   * which must be of the form 'target.method:optional parameter', where
   * target has been previously added to the list of targets of the group.
   */
  final public ControlElement addAction(int _type, String _method) {
    // A special entry point for Ejs
    if(getProperty("_ejs_")!=null) {              //$NON-NLS-1$
      _method = "_ejs_.execute(\""+_method+"\")"; //$NON-NLS-1$ //$NON-NLS-2$
    }
    Object target = null;
    MethodWithOneParameter secondAction = null;
    String parts[] = MethodWithOneParameter.splitMethodName(_method);
    if(parts==null) {
      System.err.println(getClass().getName()+" : Error! Method <"+_method+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
      return this;
    }
    if(parts[0]==null) {
      parts[0] = "_default_"; //$NON-NLS-1$
    }
    if(myGroup!=null) {
      target = myGroup.getTarget(parts[0]);
      // Only ACTIONs can have a second ACTION
      if((_type==ACTION)&&(getProperty("_ejs_SecondAction_")!=null)&&(myGroup.getTarget("_default_")!=null)) { //$NON-NLS-1$ //$NON-NLS-2$
        secondAction = new MethodWithOneParameter(_type, myGroup.getTarget("_default_"), //$NON-NLS-1$
          getProperty("_ejs_SecondAction_"), null, null, this);                          // null = void , null= no 2nd action //$NON-NLS-1$
      }
    }
    if(target==null) {
      System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
      return this;
    }
    if(parts[2]==null) {
      return addAction(_type, target, parts[1]+"()", secondAction); //$NON-NLS-1$
    }
    return addAction(_type, target, parts[1]+"("+parts[2]+")", secondAction); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /**
   * Removes an action. If the action does not exists, it does nothing.
   * <p>
   * @param     int _type      The action type
   * @param     Object _target The object whose method will be invoked
   * @param     String _method The method to call in the target object.
   * @see addAction(int,Object,String)
   */
  final public void removeAction(int _type, Object _target, String _method) {
    if(_method==null) {
      return;
    }
    for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
      MethodWithOneParameter meth = e.nextElement();
      if(meth.equals(_type, _target, _method)) {
        if(!myActionsList.removeElement(meth)) {
          System.err.println(getClass().getName()+": Error! Action "+_method+" not removed"); //$NON-NLS-1$ //$NON-NLS-2$
        }
        return;
      }
    }
  }

  /**
   * Similar to removeAction but extracts the target from the method
   */
  final public void removeAction(int _type, String _method) {
    if(_method==null) {
      return;
    }
    // A special entry point for Ejs
    if(getProperty("_ejs_")!=null) {              //$NON-NLS-1$
      _method = "_ejs_.execute(\""+_method+"\")"; //$NON-NLS-1$ //$NON-NLS-2$
    }
    String parts[] = MethodWithOneParameter.splitMethodName(_method);
    if(parts==null) {
      System.err.println(getClass().getName()+" : Error! Method <"+_method+"> not removed"); //$NON-NLS-1$ //$NON-NLS-2$
      return;
    }
    if(parts[0]==null) {
      parts[0] = "_default_"; //$NON-NLS-1$
    }
    Object target = null;
    if(myGroup!=null) {
      target = myGroup.getTarget(parts[0]);
    }
    if(target==null) {
      System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
      return;
    }
    removeAction(_type, target, parts[1]+"("+parts[2]+")"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /**
   * Invokes all actions of type ACTION
   */
  final public void invokeActions() {
    invokeActions(ControlElement.ACTION);
  }

  /**
   * Invokes all actions of this BasicControl of a given type
   * @param     int _type  The action type
   */
  final public void invokeActions(int _type) {
    if(myActiveState) {
      for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
        (e.nextElement()).invoke(_type, this);
      }
    }
    // Next line (for _model actions only!) would make unnecesary the trick of the secondActions!
    // I still use this choice because otherwise a button that calls simulation.step() would update twice
    // if (myGroup!=null && myGroup.getSimulation()!=null) myGroup.getSimulation().update();
  }

  /**
   * Reports changes of internal variables but simulation doesn't update
   * Needed by RadioButtons
   * @param     int _variableIndex the index of the internal variable that changed
   * @param     Value _value the new value for the variable
   */
  final public void variableChangedDoNotUpdate(int _variableIndex, Value _value) {
    // Changing the order of next two sentences is important!!!!
    if((myGroup!=null)&&(myProperties!=null)) {
      myGroup.variableChanged(myProperties[_variableIndex], this, _value);
    }
    if(myActiveState) {
      for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
        MethodWithOneParameter method = e.nextElement();
        method.invoke(ControlElement.VARIABLE_CHANGED, this);
      }
    }
  }

  /**
   * Reports changes of internal variables
   * @param     int _variableIndex the index of the internal variable that changed
   * @param     Value _value the new value for the variable
   */
  final public void variableChanged(int _variableIndex, Value _value) {
    if(myMethodsForProperties[_variableIndex]!=null) { // AMAVP
      // System.out.println ("Do not update because of method "+myMethodsForProperties[_variableIndex].toString());
      return;
    }
    variableChangedDoNotUpdate(_variableIndex, _value);
    // Next line should apply only to model actions, but these are the only ones expected.
    // Assigning a slider a simulation.pause() would trigger a lot of update()s!
    if((myGroup!=null)&&(myGroup.getSimulation()!=null)) {
      myGroup.getSimulation().update();
    }
  }

  /**
   * Reports changes of more than one internal variables
   * @param     int[] _variableIndexes the indexes of the internal variables that changed
   * @param     Value[] _value the new values for the variables
   */
  final public void variablesChanged(int[] _variableIndex, Value[] _value) {
    boolean doMore = false;
    if((myGroup!=null)&&(myProperties!=null)) {
      for(int i = 0; i<_variableIndex.length; i++) {
        if(myMethodsForProperties[_variableIndex[i]]==null) { // AMAVP
          // System.out.println ("Do not update this one because of method "+myMethodsForProperties[_variableIndex[i]].toString());
          myGroup.variableChanged(myProperties[_variableIndex[i]], this, _value[i]);
          doMore = true;
        }
      }
    }
    if(!doMore) {
      return; // AMAVP Nothing has changed
    }
    if(myActiveState) {
      for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
        MethodWithOneParameter method = e.nextElement();
        method.invoke(ControlElement.VARIABLE_CHANGED, this);
      }
    }
    if((myGroup!=null)&&(myGroup.getSimulation()!=null)) {
      myGroup.getSimulation().update();
    }
  }

  /**
   * Sets whether a <code>ControlElement</code> actually invokes actions.
   * The default is true.
   * @param   boolean _active Whether it is active
   */
  final public void setActive(boolean _act) {
    myActiveState = _act;
  }

  /**
   * Returns the active status of the <code>ControlElement</code>.
   */
  final public boolean isActive() {
    return myActiveState;
  }

  // ------------------------------------------------
  // Group behavior
  // ------------------------------------------------

  /**
   * Sets the GroupControl in which to operate
   * @param GroupControl _group   The GroupControl
   */
  final public void setGroup(GroupControl _group) {
    myGroup = _group;
  }

  /**
   * Gets the GroupControl in which it operates
   * @return the GroupControl
   */
  final public GroupControl getGroup() {
    return myGroup;
  }

  /**
   * Gets the Simulation in which it runs
   * @return the Simulation
   */
  final public Simulation getSimulation() {
    if(myGroup==null) {
      return null;
    }
    return myGroup.getSimulation();
  }

} // End of Class

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.
 *
 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
