/*
 * 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/>
 */

package org.opensourcephysics.ejs.control;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.ejs.Simulation;
import org.opensourcephysics.ejs.control.swing.ControlContainer;
import org.opensourcephysics.ejs.control.swing.ControlDialog;
import org.opensourcephysics.ejs.control.swing.ControlFrame;
import org.opensourcephysics.ejs.control.swing.ControlWindow;
import org.opensourcephysics.ejs.control.value.BooleanValue;
import org.opensourcephysics.ejs.control.value.DoubleValue;
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;

/**
 * A base class to group several ControlElements, connect them
 * to one or more target objects in a unified form, and build a
 * graphic interface with all of them.
 */
// Note for myself: This class is still very much dependent on the actual
// implementation of some of the particular subclasses of ControlElement,
// like ControlContainer, ControlFrame and ControlDialog.
public class GroupControl {
  public static final int DEBUG_NONE = 0;
  public static final int DEBUG_SET_AND_GET = 1;
  public static final int DEBUG_ELEMENTS = 2;
  public static final int DEBUG_CONTROL = 4;
  public static final int DEBUG_CONTROL_VERBOSE = 8;
  public static final int DEBUG_DRAWING = 16;
  public static final int DEBUG_DRAWING_VERBOSE = 32;
  public static final int DEBUG_SYSTEM = 64;
  public static final int DEBUG_SYSTEM_VERBOSE = 128;
  public static final int DEBUG_ALL = 255;
  private int debugLevel = 0;
  private String debugPrefix = "";            //$NON-NLS-1$
  protected String replaceOwnerName = null;
  private java.awt.Frame ownerFrame = null;
  protected java.awt.Frame replaceOwnerFrame = null;
  // private String     defaultPrefix = null;
  private Vector<String> prefixList = new Vector<String>();
  private Simulation mySimulation = null;
  private Hashtable<String, Object> targetTable = new Hashtable<String, Object>();
  private Hashtable<String, ControlElement> elementTable = new Hashtable<String, ControlElement>();
  Hashtable<String, GroupVariable> variableTable = new Hashtable<String, GroupVariable>();
  private Vector<ControlElement> elementList = new Vector<ControlElement>();
  private Vector<ControlElement> updateList = new Vector<ControlElement>();
  GroupVariable methodTriggerVariable = null; // AMAVP (See Note in ControlElement)

  /**
   * The default constructor.
   */
  public GroupControl() {
    debugPrefix = this.getClass().getName();
    int index = debugPrefix.lastIndexOf("."); //$NON-NLS-1$
    if(index>=0) {
      debugPrefix = debugPrefix.substring(index+1);
    }
    appendPrefixPath("org.opensourcephysics.ejs.control.swing.Control");      //$NON-NLS-1$
    appendPrefixPath("org.opensourcephysics.ejs.control.drawables.Control");  //$NON-NLS-1$
    appendPrefixPath("org.opensourcephysics.ejs.control.displayejs.Control"); //$NON-NLS-1$
    setValue(ControlElement.METHOD_TRIGGER, new BooleanValue(false));         // AMAVP
    methodTriggerVariable = variableTable.get(ControlElement.METHOD_TRIGGER); // AMAVP
  }

  /**
   * The constructor.
   * @param _target
   */
  public GroupControl(Object _target) {
    this();
    addTarget("_default_", _target); //$NON-NLS-1$
    if(_target instanceof Simulation) {
      setSimulation((Simulation) _target);
    }
  }

  /**
   * A specialized constructor for Ejs use.
   * This adds elements to it in the ususal way, but replaces a Frame element
   * of a given name by the prescribed frame.
   * @param _simulation
   * @param _replaceName
   * @param _replaceOwnerFrame
   */
  public GroupControl(Object _simulation, String _replaceName, java.awt.Frame _replaceOwnerFrame) {
    this(_simulation);
    replaceOwnerFrame(_replaceName, _replaceOwnerFrame);
  }

  // ------------------------------------------------
  // Preliminary things
  // ------------------------------------------------

  /**
   * Sets the owner frame for all subsequent Dialogs
   * @param     Frame _frame  The frame that should own next Dialogs
   *   (if there are Dialogs in this group)
   */
  public void setOwnerFrame(java.awt.Frame _frame) {
    ownerFrame = _frame;
  }

  /**
   * Returns the owner frame for all subsequent Dialogs
   */
  public java.awt.Frame getOwnerFrame() {
    return ownerFrame;
  }

  public void replaceOwnerFrame(String _replaceName, java.awt.Frame _replaceOwnerFrame) {
    replaceOwnerName = _replaceName;
    replaceOwnerFrame = _replaceOwnerFrame;
  }

  /**
   * Returns the name of the replacement for the owner frame for all subsequent Dialogs
   */
  public String getReplaceOwnerName() {
    return replaceOwnerName;
  }

  /**
   * Returns the replacement for the owner frame for all subsequent Dialogs
   */
  public java.awt.Frame getReplaceOwnerFrame() {
    return replaceOwnerFrame;
  }

  /**
   * Clears the list of default package name for unqualified elements
   */
  public void clearPrefixPath() {
    prefixList.clear();
  }

  /**
   * Adds a prefix 'path' for unqualified elements. Hence "Frame"
   * becomes _prefix + "Frame". The default list includes
   * "org.opensourcephysics.ejs.control.swing.Control" and
   * "org.opensourcephysics.ejs.control.drawables.Control"
   * @param     String _prefix  The prefix to be added to list
   */
  public void appendPrefixPath(String _prefix) {
    prefixList.add(_prefix);
  }

  /**
   * Returns the list (actually, a vector) of prefix
   */
  public Vector<String> getDefaultPrefixList() {
    return prefixList;
  }

  /**
   * Sets the simulation under which the control is running
   * This is used to up date the simulation whenever an Element changes a
   * variable (See variableChanged in ControlElement)
   * @param     Simulation _sim  The simulation
   */
  public void setSimulation(Simulation _sim) {
    mySimulation = _sim;
  }

  /**
   * Returns the simulation under which the control is running
   * This is used to up date the simulation whenever an Element changes a
   * variable (See variableChanged in ControlElement
   */
  public Simulation getSimulation() {
    return mySimulation;
  }

  /**
   * Sets the debug level
   * @param     int _level  The minimim level that should
   * produce debug mesagges. Must be one of
   * DEBUG_NONE, DEBUG_SET_AND_GET, DEBUG_ELEMENTS,
   * DEBUG_ALL=255
   */
  public void setDebugLevel(int _level) {
    debugLevel = _level;
  }

  /**
   * Returns the current debug level
   * @return     The actual minimim level that produces debug messages
   */
  public int getDebugLevel() {
    return debugLevel;
  }

  // ------------------------------------------------
  // Targets
  // ------------------------------------------------

  /**
   * Returns one of the registered target objects
   * @param   String _name  The name given to the target when it was added
   */
  public Object getTarget(String _name) {
    return targetTable.get(_name);
  }

  /**
   * Adds an object to be controlled. Actions can then refer to methods of the
   * form 'name.method:parameter'
   * @param   String _name  A name to refer to the target
   * @param   Object _target  A target object
   */
  public void addTarget(String _name, Object _target) {
    targetTable.put(_name, _target);
  }

  /**
   * Removes a target object
   */
  public void removeTarget(String _name) {
    targetTable.remove(_name);
  }

  // --------------------------------------------------------
  // Dealing with group variables
  // --------------------------------------------------------

  /**
   * Sets the group value for a variable. This includes the value in all
   * the elements of this group that are registered to this variable name.
   * @param   String _name  The variable name
   * @param   Value _value  The value as a <code>Value</code> object
   */
  public void setValue(String _name, Value _value) {
    // I have commented out the debug messages to improve performance (??).
    // Uncomment them if you want further debug messages
    // if ((debugLevel & DEBUG_SET_AND_GET)>0)
    // System.out.print(debugPrefix+": Setting value of <"+_name+"> to <"+_value+"> ...");
    // if (_name==null) return;
    GroupVariable variable = variableTable.get(_name);
    if(variable==null) {
      variable = new GroupVariable(_name, _value);
      variableTable.put(_name, variable);
      // if ((debugLevel & DEBUG_SET_AND_GET)>0) System.out.println("Created <"+_name+"> with value <"+_value+">");
    } else {
      variable.setValue(_value);
      variable.propagateValue(null);
      // if ((debugLevel & DEBUG_SET_AND_GET)>0) System.out.println("<"+_name+"> set to <"+_value+">");
    }
  }

  /**
   * Returns the group value of a variable.
   * @return the <code>Value</code> object of the variable. If the
   *         variable has never been set, it returns <b>null</b>.
   * @param  String _name  The variable name
   */
  public Value getValue(String _name) {
    // I have commented the debug messages out to improve performance (??).
    // Uncomment them if you want further debug messages
    // if ((debugLevel & DEBUG_SET_AND_GET)>0)
    // System.out.print(debugPrefix+": Getting value of <"+_name+ ">...");
    GroupVariable variable = variableTable.get(_name);
    if(variable==null) {
      // if ((debugLevel & DEBUG_SET_AND_GET)>0) System.out.println("<"+_name+"> has not been set!");
      return null;
    }
    // if ((debugLevel & DEBUG_SET_AND_GET)>0) System.out.println("<"+_name+"> found with value = <"+variable.value+">");
    return variable.getValue();
  }

  /**
   * Associates an element internal value with a variable name. Later on,
   * when the user sets the value for this variable, either
   * programmatically or by interaction with a given element,
   * all registered elements will be informed of the change.
   * Invoked by ControlElements when processing the 'variable'
   * property. Not to be used directly by users.
   * @param     String _name  The name of the variable
   * @param     ControlElement _element  The element to be registered
   * @param     int _index  An indentifier for the element internal value
   * @param     Value _value The initial value if the variable doesn't already exist
   */
  public GroupVariable registerVariable(String _name, ControlElement _element, int _index, Value _value) {
    // if ((debugLevel & DEBUG_SET_AND_GET)>0)
    // System.out.print(debugPrefix+": Registering variable <"+_name
    // + "> for element <"+_element+">  at index "+_index+" with value = "+_value);
    if(_name==null) {
      return null;
    }
    GroupVariable variable = variableTable.get(_name);
    if(variable==null) {
      variable = new GroupVariable(_name, _value);
      variableTable.put(_name, variable);
      if((debugLevel&DEBUG_SET_AND_GET)>0) {
        System.out.print("   Created new variable <"+_name+"> with value = <"+_value+"> ..."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      }
    }
    // else variable.setValue(_value); // Commented means that the element takes
    // the actual value, whatever it is
    // System.out.println("   variable <"+_name+"> has value = <"+getValue(_name)+"> ...");
    if((debugLevel&DEBUG_SET_AND_GET)>0) {
      System.out.println("   Variable <"+_name+"> registered for element <"+_element+">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }
    variable.addElementListener(_element, _index);
    variable.propagateValue(null); // null implies that the element takes the actual value
    return variable;
  }

  /**
   * Tells whether a variable is associated to any element.
   * Invoked by EjsControl's method 'setValue/getValue'.
   * Not to be used directly by users.
   * @param     ControlElement _element  The element to be included
   * @param     String _variable  The variable name
   */
  public boolean isVariableRegistered(String _name) {
    if(_name==null) {
      return false;
    }
    return(variableTable.get(_name)!=null);
  }

  /**
   * Invoked by ControlElements when their internal variables change.
   * Not be used directly by users.
   */
  public void variableChanged(GroupVariable _variable, ControlElement _element, Value _value) {
    if(_variable==null) {
      return;
    }
    _variable.setValue(_value);
    _variable.propagateValue(_element);
    _variable.invokeListeners(_element); // Call any registered listener
  }

  public void addListener(String _name, String _method) {
    addListener(_name, _method, null);
  }

  /**
   * Instructs the group to invoke a method (with an optional parameter) when a
   * variable changes.
   * @param  String _name   The name of the variable that may change
   * @param  String _method  The method that should be called in the controlled
   * @param Object _anObject the object to pass in the special case the method is method(#CONTROL#)
   * object.
   */
  public void addListener(String _name, String _method, Object _anObject) {
    if((debugLevel&DEBUG_SET_AND_GET)>0) {
      System.out.print(debugPrefix+": Adding listener for variable <"+_name+"> to <"+_method+"> ..."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }
    if(_name==null) {
      return;
    }
    String[] parts = MethodWithOneParameter.splitMethodName(_method);
    if(parts==null) {
      System.err.println(getClass().getName()+" : Error! Listener <"+_method+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
      return;
    }
    if(parts[0]==null) {
      parts[0] = "_default_"; //$NON-NLS-1$
    }
    Object target = getTarget(parts[0]);
    if(target==null) {
      System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
      return;
    } else if((debugLevel&DEBUG_SET_AND_GET)>0) {
      System.out.print(debugPrefix+": Target <"+parts[0]+"> found. Method is <"+_method+"> ..."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }
    GroupVariable variable = variableTable.get(_name);
    if(variable==null) {
      variable = new GroupVariable(_name, doubleValue);
      variableTable.put(_name, variable);
      if((debugLevel&DEBUG_SET_AND_GET)>0) {
        System.out.print("   Created new variable <"+_name+"> for listener <"+_method+"> ..."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      }
    }
    if(parts[2]==null) {
      variable.addListener(target, parts[1]+"()", _anObject);             //$NON-NLS-1$
    } else {
      variable.addListener(target, parts[1]+"("+parts[2]+")", _anObject); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  // --------------------------------------------------------
  // Adding and removing control elements
  // --------------------------------------------------------

  /**
   * Renaming a ControlElement
   * @param     String _name   The new name for the element.
   */
  public void rename(ControlElement _element, String _name) {
    String oldName = _element.getProperty("name"); //$NON-NLS-1$
    if(oldName!=null) {
      elementTable.remove(oldName);
    }
    if(_name!=null) {
      elementTable.put(_name, _element);
    }
  }

  /**
   * Creates a new ControlElement with a given name
   * This is a special feature that is used by LauncherApplet, so that
   * if the name coincides with a given one, a Frame becomes a Panel,
   * so that it can be captured!
   * @param     String _type   The class name of the new element.
   * @param     String _name   The name of the new element.
   */
  final public ControlElement addNamed(String _type, String _name) {
    String propertyList = "name="+_name; //$NON-NLS-1$
    if((replaceOwnerName==null)||!replaceOwnerName.equals(_name)) {
      return addObject(null, _type, propertyList);
    }
    if(_type.endsWith("ControlFrame")||_type.endsWith("ControlDrawingFrame")) {                     //$NON-NLS-1$ //$NON-NLS-2$
      setOwnerFrame(replaceOwnerFrame);
      return addObject(null, "org.opensourcephysics.ejs.control.swing.ControlPanel", propertyList); //$NON-NLS-1$
    }
    return addObject(null, _type, propertyList);
  }

  /**
   * Creates a new ControlElement
   * @param     String _type   The class name of the new element.
   * If it is not qualified, then it is given the prefix (see above)
   */
  final public ControlElement add(String _type) {
    return addObject(null, _type, null);
  }

  /**
   * Creates a new ControlElement and gives it a name
   * @param     String _type   The class name of the new element.
   * If it is not qualified, then it is given the default prefix (see above)
   * @param     String _propertyList A list of properties and Values
   *            to be set (see ControlElement.setProperties())
   */
  final public ControlElement add(String _type, String _propertyList) {
    return addObject(null, _type, _propertyList);
  }

  /**
   * Creates a new ControlElement that wrapps an existing object
   * If the object is not of the right class it will print a warning
   * and ignore the object provided.
   * @param     Object _object   The element to be wrapped
   * @param     String _type   The class name of the new element.
   * If it is not qualified, then it is given the prefix (see above)
   */
  final public ControlElement addObject(Object _object, String _type) {
    return addObject(_object, _type, null);
  }

  /**
   * Creates a new ControlElement that wrapps an existing object
   * If the object is not of the right class it will print a warning
   * and ignore the object provided.
   * @param     Object _object   The element to be wrapped
   * @param     String _type   The class name of the new element.
   *            If it is not qualified, then it is given the prefix (see above)
   * @param     String _propertyList A list of properties and Values
   *            to be set (see ControlElement.setProperties())
   */
  public ControlElement addObject(Object _object, String _type, String _propertyList) {
    ControlElement element = null;
    if((debugLevel&DEBUG_ELEMENTS)>0) {
      System.err.println(this.getClass().getName()+" Adding element of type <"+_type+"> with properties <"+_propertyList+">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      if(_object!=null) {
        System.err.println(this.getClass().getName()+" using element "+_object); //$NON-NLS-1$
      }
    }
    if(_type.indexOf(".")<0) {                                             //$NON-NLS-1$
      for(Enumeration<String> e = prefixList.elements(); e.hasMoreElements()&&(element==null); ) {
        element = instantiateClass(_object, e.nextElement()+_type, false); // Silently
      }
    }
    if(element==null) {
      element = instantiateClass(_object, _type, true);
    }
    if(element==null) {
      return null;
    }
    // Frames become automatically ownerFrames for subsequent Dialogs
    if(element instanceof ControlFrame) {
      setOwnerFrame((java.awt.Frame) ((ControlFrame) element).getComponent());
    }
    // Use ownerFrame for Dialogs, if there is any
    if((element instanceof ControlDialog)&&(ownerFrame!=null)) {
      ((javax.swing.JDialog) ((ControlDialog) element).getComponent()).dispose();
      ((ControlDialog) element).replaceVisual(ownerFrame);
    }
    element.setGroup(this);
    elementList.add(element);
    if(element instanceof NeedsUpdate) {
      updateList.add(element);
    }
    if((debugLevel&DEBUG_ELEMENTS)>0) {
      System.err.println(this.getClass().getName()+" Setting properties to <"+_propertyList+">"); //$NON-NLS-1$ //$NON-NLS-2$
    }
    if(_propertyList!=null) {
      element.setProperties(_propertyList);
    }
    if(element instanceof ControlWindow) {       // Make windows visible by default
      if(element.getProperty("visible")==null) { //$NON-NLS-1$
        element.setProperty("visible", "true");  //$NON-NLS-1$ //$NON-NLS-2$
      }
    }
    return element;
  }

  private ControlElement instantiateClass(Object _object, String _classname, boolean _verbose) {
    if((debugLevel&DEBUG_ELEMENTS)>0) {
      System.err.println(this.getClass().getName()+": Trying to instantiate element of class "+_classname); //$NON-NLS-1$
      if(_object!=null) {
        System.err.println(this.getClass().getName()+" using element "+_object);                            //$NON-NLS-1$
      }
    }
    // if (_object!=null) // Even if the object to wrapp is null!
    try {
      Class<?> aClass = Class.forName(_classname);
      Class<?>[] c = {Object.class};
      Object[] o = {_object};
      java.lang.reflect.Constructor<?> constructor = aClass.getDeclaredConstructor(c);
      return(ControlElement) constructor.newInstance(o);
    } catch(Exception _exc) {
      if(_verbose) {
        _exc.printStackTrace();
        return null;
      } // Do not exit if in silent mode
    }
    // Try once more with the default constructor
    try {
      Class<?> aClass = Class.forName(_classname);
      return(ControlElement) aClass.newInstance();
    } catch(Exception _exc) {
      if(_verbose) {
        _exc.printStackTrace();
      }
      return null;
    }
  }

  /**
   * Returns a control element by name
   * @return    the ControlElement if found, null otherwise.
   * @param     String _name  The name of the control element
   */
  public ControlElement getElement(String _name) {
    if(_name==null) {
      return null;
    }
    ControlElement element = elementTable.get(_name);
    if(element==null) {
      // Commented out to improve performance
      // if ((debugLevel & DEBUG_ELEMENTS)>0) {
      // System.err.println(this.getClass().getName()+" Warning!: control element <"+_name+"> not found!");
      // System.err.println("  List of named elements follows: ");
      // for (Enumeration e = elementTable.elements() ; e.hasMoreElements() ;) {
      // element = (ControlElement) e.nextElement();
      // String name = element.getProperty("name");
      // if (name!=null) System.err.println("    "+name+"(class is "+element.getClass().getName()+")");
      // }
      // }
      return null;
    }
    return element;
  }

  // For backwards compatibility
  public ControlElement getControl(String _name) {
    return getElement(_name);
  }

  /**
   * Returns the visual of a control element by name
   * @return    the java.awt.Component visual of the element if found, null otherwise.
   * @param     String _name  The name of the control element
   */
  public java.awt.Component getVisual(String _name) {
    ControlElement element = getElement(_name);
    if(element==null) {
      return null;
    }
    return element.getVisual();
  }

  /**
   * Returns the component of a control element by name
   * @return    the java.awt.Component component of the element if found, null otherwise.
   * @param     String _name  The name of the control element
   */
  public java.awt.Component getComponent(String _name) {
    ControlElement element = getElement(_name);
    if(element==null) {
      return null;
    }
    return element.getComponent();
  }

  /**
   * Returns the container of a control element by name
   * @return    the java.awt.Container visual of the element if found, and the
   * element is a container, null otherwise.
   * @param     String _name  The name of the control element
   */
  public java.awt.Container getContainer(String _name) {
    ControlElement element = getElement(_name);
    if(element instanceof ControlContainer) {
      return((ControlContainer) element).getContainer();
    }
    return null;
  }

  /**
   * Completely destroy a ControlElement by name
   * @param     String _name  The name of the ControlElement to be destroyed
   */
  public void destroy(String _name) {
    destroy(getElement(_name), true);
  }

  /**
   * Completely destroy a ControlElement
   * @param     ControlElement _element The ControlElement to be destroyed
   */
  public void destroy(ControlElement _element) {
    destroy(_element, true);
  }

  /**
   * Reset all elements
   */
  public void reset() {
    for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
      // ControlElement ele = (ControlElement ) e.nextElement();
      // System.out.println ("Resettimng element "+ele);
      // ele.reset();
      e.nextElement().reset();
    }
  }

  /**
   * Initialize all elements
   */
  public void initialize() {
    for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
      e.nextElement().initialize();
    }
  }

  /**
   * Refresh all elements
   */
  public void update() {
    methodTriggerVariable.propagateValue(null); // AMAVP (See Note in ControlElement)
    // setValue (ControlElement.METHOD_TRIGGER,true);
    // for (Enumeration e=elementList.elements(); e.hasMoreElements(); ) ((ControlElement) e.nextElement()).update();
    for(Enumeration<ControlElement> e = updateList.elements(); e.hasMoreElements(); ) {
      ((NeedsUpdate) e.nextElement()).update();
    }
  }

  /**
   * Set the active state of all elements
   */
  public void setActive(boolean _active) {
    for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
      e.nextElement().setActive(_active);
    }
  }

  /**
   * Clears all variables
   */
  public void clearVariables() {
    variableTable.clear();
  }

  /**
   * Destroy all elements
   */
  public void clear() {
    variableTable.clear();
    setOwnerFrame(null);
    for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
      ControlElement element = e.nextElement();
      String parent = element.getProperty("parent"); //$NON-NLS-1$
      if(parent==null) {
        destroy(element, false);
      }
    }
    if((debugLevel&DEBUG_ELEMENTS)>0) {
      System.err.println(this.getClass().getName()+" Warning!: All element were destroyed!");        //$NON-NLS-1$
      System.err.println("  List of remaining elements follows: ");                                  //$NON-NLS-1$
      for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
        ControlElement element = e.nextElement();
        System.err.println("    "+element.toString()+"(class is "+element.getClass().getName()+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      }
    }
  }

  private void destroy(ControlElement _element, boolean _informMyParent) {
    if(_element==null) {
      return;
    }
    if(_informMyParent) {
      ControlElement parent = getElement(_element.getProperty("parent")); //$NON-NLS-1$
      if(parent!=null) {
        if(parent instanceof ControlContainer) {
          ((ControlContainer) parent).remove(_element);
        }
      } else {                                                            // It may have been added to a container programmatically
        java.awt.Container cont = _element.getComponent().getParent();
        if(cont!=null) {
          cont.remove(_element.getComponent());
          cont.validate();
          cont.repaint();
        }
      }
    }
    _element.variablePropertiesClear();
    String name = _element.getProperty("name"); //$NON-NLS-1$
    if(name!=null) {
      elementTable.remove(name);
    }
    elementList.remove(_element);
    if(_element instanceof NeedsUpdate) {
      updateList.remove(_element);
    }
    if(_element instanceof ControlContainer) {
      for(Enumeration<ControlElement> e = ((ControlContainer) _element).getChildren().elements(); e.hasMoreElements(); ) {
        ControlElement child = e.nextElement();
        destroy(child, false);
      }
    }
    if(_element instanceof ControlWindow) {
      ((ControlWindow) _element).dispose();
    }
  }

  /**
   * Returns the top-level ancestor of an element (either the
   * containing Window or Applet), or null if the element has not
   * been added to any container. If no element name is provided, the
   * first control element whose component is a Window is returned.
   * @return    the Container if found, null otherwise.
   * @param     String _name  The name of the control element
   */
  public java.awt.Container getTopLevelAncestor(String _name) {
    if(_name!=null) {
      ControlElement element = getElement(_name);
      java.awt.Component comp = element.getComponent();
      if(comp instanceof javax.swing.JComponent) {
        return((javax.swing.JComponent) comp).getTopLevelAncestor();
      }
    } else {
      for(Enumeration<ControlElement> e = elementList.elements(); e.hasMoreElements(); ) {
        ControlElement element = e.nextElement();
        java.awt.Component comp = element.getComponent();
        if(comp instanceof java.awt.Window) {
          return(java.awt.Window) comp;
        }
      }
    }
    return null;
  }

  // -------------------------------------------------------
  // Convenience methods
  // Half way to org.opensourcephysics.controls.Control
  // -------------------------------------------------------
  // For the custom methods
  private BooleanValue booleanValue = new BooleanValue(false);
  private IntegerValue integerValue = new IntegerValue(0);
  private DoubleValue doubleValue = new DoubleValue(0.0);
  private StringValue stringValue = new StringValue(""); //$NON-NLS-1$
  private ObjectValue objectValue = new ObjectValue(null);

  // --------- Setting different types of values ------

  /**
   * A convenience method to set a value to a boolean
   * @param _name
   * @param _value
   */
  public void setValue(String _name, boolean _value) {
    booleanValue.value = _value;
    setValue(_name, booleanValue);
  }

  /**
   * A convenience method to set a value to an int
   * @param _name
   * @param _value
   */
  public void setValue(String _name, int _value) {
    integerValue.value = _value;
    setValue(_name, integerValue);
  }

  /**
   * A convenience method to set a value to a double
   * @param _name
   * @param _value
   */
  public void setValue(String _name, double _value) {
    doubleValue.value = _value;
    setValue(_name, doubleValue);
  }

  /**
   * A convenience method to set a value to a String
   * @param _name
   * @param _value
   */
  public void setValue(String _name, String _value) {
    stringValue.value = _value;
    setValue(_name, stringValue);
  }

  /**
   * A convenience method to set a value to any Object
   * @param _name
   * @param _value
   */
  public void setValue(String _name, Object _value) {
    if(_value instanceof String) {
      setValue(_name, (String) _value);
    } else {
      objectValue.value = _value;
      setValue(_name, objectValue);
    }
  }

  // --------- Getting different types of values ------

  /**
   * A convenience method to get a value as a boolean
   * @param _name
   */
  public boolean getBoolean(String _name) {
    Value value = getValue(_name);
    if(value==null) {
      return false;
    }
    return value.getBoolean();
  }

  /**
   * A convenience method to get a value as an int
   * @param _name
   */
  public int getInt(String _name) {
    Value value = getValue(_name);
    if(value==null) {
      return 0;
    }
    return value.getInteger();
  }

  /**
   * A convenience method to get a value as a double
   * @param _name
   */
  public double getDouble(String _name) {
    Value value = getValue(_name);
    if(value==null) {
      return 0.0;
    }
    return value.getDouble();
  }

  /**
   * A convenience method to get a value as a String
   * @param _name
   */
  public String getString(String _name) {
    Value value = getValue(_name);
    if(value==null) {
      return ""; //$NON-NLS-1$
    }
    return value.getString();
  }

  /**
   * A convenience method to get a value as an Object
   * @param _name
   */
  public Object getObject(String _name) {
    Value value = getValue(_name);
    if(value==null) {
      return null;
    }
    return value.getObject();
  }

  /**
   * Returns an XML.ObjectLoader to save and load data for this object.
   *
   * @return the object loader
   */
  public static XML.ObjectLoader getLoader() {
    return new GroupControlLoader();
  }

  /**
   * A class to save and load XML data for GroupControl.
   */
  static class GroupControlLoader implements XML.ObjectLoader {
    /**
     * Saves object data to an XMLControl.
     *
     * @param control the control to save to
     * @param obj the object to save
     */
    public void saveObject(XMLControl control, Object obj) {
      GroupControl groupcontrol = (GroupControl) obj;
      Hashtable<String, GroupVariable> table = groupcontrol.variableTable;
      Iterator<String> it = table.keySet().iterator();
      while(it.hasNext()) {
        String name = it.next();
        if(!name.startsWith("_")) { // don't save ejs internal variables //$NON-NLS-1$
          if(groupcontrol.getObject(name).getClass().isArray()) {
            control.setValue(name, groupcontrol.getObject(name));
          } else {
            control.setValue(name, groupcontrol.getString(name));
          }
        }
      }
    }

    /**
     * Creates an object using data from an XMLControl.
     *
     * @param control the control
     * @return the newly created object
     */
    public Object createObject(XMLControl control) {
      return new GroupControl(null);
    }

    /**
     * Loads an object with data from an XMLControl.
     *
     * @param control the control
     * @param obj the object
     * @return the loaded object
     */
    public Object loadObject(XMLControl control, Object obj) {
      GroupControl groupcontrol = (GroupControl) obj;
      Hashtable<String, GroupVariable> table = groupcontrol.variableTable;
      Iterator<String> it = table.keySet().iterator();
      while(it.hasNext()) {
        String name = it.next();
        if(control.getString(name)!=null) {
          groupcontrol.setValue(name, control.getString(name));
        } else if(control.getObject(name)!=null) {
          Object namedObj = control.getObject(name);
          if(namedObj instanceof java.awt.Color) {
            groupcontrol.setValue(name, namedObj);
          }
          if(namedObj.getClass().isArray()) {
            groupcontrol.setValue(name, namedObj);
          } else {
            groupcontrol.setValue(name, namedObj.toString());
          }
        }
      }
      return obj;
    }

  }

} // 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
 */
