/*
 * 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.controls;
import java.awt.Font;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.JTextArea;

/**
 * Title:        ParsableTextArea
 * Description:  JTextArea with the ability to parse parameters.
 */
public class ParsableTextArea extends JTextArea {
  HashMap<String, String> pendingMap = new LinkedHashMap<String, String>();
  HashMap<CharSequence, CharSequence> currentMap = new LinkedHashMap<CharSequence, CharSequence>();
  HashMap<CharSequence, CharSequence> lockedMap = new LinkedHashMap<CharSequence, CharSequence>();
  boolean locked = false;

  /**
   * ParsableTextArea constructor.
   *
   */
  public ParsableTextArea() {
    super(10, 10);
    setFont(new Font("Monospaced", Font.PLAIN, 12)); //$NON-NLS-1$
  }

  /**
   * Gets the stored string parameter associated with the variable name.
   *
   * @param variable
   *
   * @return the string
   *
   * @throws VariableNotFoundException
   */
  public String getValue(String variable) throws VariableNotFoundException {
    if(locked) {
      synchronized(lockedMap) {
        String val = (String) lockedMap.get(variable);
        if(val!=null) {
          return(String) lockedMap.get(variable);
        }
        throw new VariableNotFoundException("Variable "+variable+" not found."); //$NON-NLS-1$ //$NON-NLS-2$
      }
    }
    synchronized(currentMap) {
      updateCurrentMap();              // gets existing values
      synchronized(pendingMap) {
        currentMap.putAll(pendingMap); // add pending values
      }
      String val = (String) currentMap.get(variable);
      if(val!=null) {
        return val;
      }
    }
    throw new VariableNotFoundException("Variable "+variable+" not found."); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /**
   * Locks values to those currently on display.
   * @param lock boolean
   */
  public synchronized void setLockValues(boolean lock) {
    if(locked==lock) {
      return; // no change so don't do anything
    }
    locked = lock;
    if(locked) {                        // get all current and pending values
      synchronized(lockedMap) {
        lockedMap.clear();
        synchronized(currentMap) {
          updateCurrentMap();
          lockedMap.putAll(currentMap); // add current values
        }
        synchronized(pendingMap) {
          lockedMap.putAll(pendingMap); // add pending values
        }
      }
    } else {                            // control has just been unlocked
      setValue(null, null);             // updates the text area
    }
  }

  /**
   * Stores a variable name and an associated value.
   *
   * @param variable
   * @param val
   */
  public void setValue(String variable, String val) {
    Runnable doLater = new Runnable() {
      public void run() {
        updateText();
      }

    };
    if(variable!=null) {
      synchronized(pendingMap) {
        pendingMap.put(variable, val);
      }
    }
    if(locked&&(variable!=null)) {
      synchronized(lockedMap) {
        lockedMap.put(variable, val);
      }
    }
    if(!locked) {
      java.awt.EventQueue.invokeLater(doLater);
    }
  }

  public HashMap<CharSequence, CharSequence> getCurrentMap() {
    synchronized(currentMap) {
      updateCurrentMap();                  // gets existing values
      synchronized(pendingMap) {
        currentMap.putAll(pendingMap);     // add pending values
      }
      return new HashMap<CharSequence, CharSequence>(currentMap);
    }
  }

  private synchronized void updateText() { // should only be called from event queue
    synchronized(currentMap) {
      synchronized(pendingMap) {
        if(pendingMap.size()==0) {
          return;
        }
        updateCurrentMap();        // puts existing variables into currentMap
        currentMap.putAll(pendingMap);
        pendingMap.clear();
      }
      Set<CharSequence> set = currentMap.keySet();
      Iterator<CharSequence> it = set.iterator();
      StringBuffer newText = new StringBuffer(set.size()*25);
      while(it.hasNext()) {
        String variable = (String) it.next();
        newText.append(variable);
        newText.append('=');
        newText.append(currentMap.get(variable));
        newText.append('\n');
      }
      setText(newText.toString()); // access text area because we are in event queue
    }
  }

  private void updateCurrentMap() {
    currentMap.clear();
    String text = getText();
    StringTokenizer st = new StringTokenizer(text, "\n"); //$NON-NLS-1$
    while(st.hasMoreTokens()) {
      String aLine = st.nextToken().trim();
      int index = aLine.indexOf("="); //$NON-NLS-1$
      if(index!=-1) {
        currentMap.put(aLine.subSequence(0, index), aLine.subSequence(index+1, aLine.length()));
      }
    }
  }

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

  /**
   * A class to save and load data for OSPControls.
   */
  static class ParsableTextAreaLoader 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) {
      Map<CharSequence, CharSequence> map = ((ParsableTextArea) obj).getCurrentMap();
      Iterator<CharSequence> it = map.keySet().iterator();
      while(it.hasNext()) {
        String variable = (String) it.next();
        control.setValue(variable, map.get(variable));
      }
    }

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

    /**
     * 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) {
      ParsableTextArea pta = (ParsableTextArea) obj;
      // iterate over properties and add them to pts
      Iterator<String> it = control.getPropertyNames().iterator();
      pta.setLockValues(true);
      while(it.hasNext()) {
        String variable = it.next();
        pta.setValue(variable, control.getString(variable));
      }
      pta.setLockValues(false);
      return obj;
    }

  }

}

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