/* ========================
 * JSynoptic : a free Synoptic editor
 * ========================
 *
 * Project Info:  http://jsynoptic.sourceforge.net/index.html
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * (C) Copyright 2001-2003, by :
 *     Corporate: 
 *         Astrium SAS 
 *         EADS CRC
 *     Individual: 
 *         Nicolas Brodu
 *
 * $Id: RandomDataSourceCollection.java,v 1.4 2008/02/25 11:33:36 ogor Exp $
 *
 * Changes
 * -------
 * 27-Nov-2003 : Creation Date (NB);
 *
 */
package examples.random;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
import javax.swing.undo.CompoundEdit;
import jsynoptic.base.ContextualActionProvider;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DynamicDataSourceCollection;
import simtools.data.UnsupportedOperation;
import simtools.data.ValueProvider;
import simtools.data.buffer.DelayedBuffer;
/**
 * This example class can serves as the basis to create your own dynamic data sources.
 * It creates data sources of random data between 0 and 100 on demand.
 * 
 * The following optional interfaces are implemented as an example.
 * - ContextualActionProvider : adds a contextual popup menu when right-clicking on the collection in the source pane
 * 
 */
public class RandomDataSourceCollection extends DynamicDataSourceCollection  implements ActionListener, ContextualActionProvider {
  /** We use a timer to produce values periodically
   */
  protected Timer timer;
  
  /** 
   * This constructor sets up the timer and one time source.
   * The time source is used to create XY plots, so we can see 
   * the evolution of random variables over time.
   */
  public RandomDataSourceCollection() {
    // Create a first source called "time", useful to make XY plots.
    // See addSource() for comments
    createDataSource(new DataInfo("time","time","the time","s"), ValueProvider.DoubleProvider);
    // Wrap a buffer around it so we see past values too
    try {
      bufferize(0, new DelayedBuffer(ValueProvider.DoubleProvider, 100));
    } catch (UnsupportedOperation e) {
      e.printStackTrace();
    }
    
    // Calls ourselves each half of a second
    timer = new Timer(500, this);
  }
  /** Starts producing data automatically 
   */
  public void start() {
    timer.start();
  }
  
  /** Stops producing data automatically 
   */
  public void stop() {
    timer.stop();
  }
  /** Produce exactly one data in each source
   */
  public void step() {
    // The API of the dynamic data source is as follow:
    // - Set the values for each source using setTypeValue
    // - Register the values when they are ready with registerNewValues
    
    // The last index used is stored in parent class => use it to compute the index for our data sources
    long curIndex = lastIndex + 1;
    
    // First source is the time
    // Simply use the index, as we're called each half second
    setDoubleValue(0, curIndex / 2.0);
    // For all other sources, add random data
    for (int i=1; i<size(); ++i) {
      // On init, start at 50
      if (curIndex==0) setDoubleValue(i,50);
      
      // Then, add or remove a random quantity, be remain bounded between 0 and 100 
      else {
        double value = sourceInfo[i].cachedDouble;
        value += Math.random()*10 -5 ;
        if (value>100) value = 100;
        if (value<0) value = 0;
        setDoubleValue(i,value);
      }
      
    }
    
    // Now register the values to the system.
    registerNewValues();
  }
  
  /** Adds a new data source to the collection
   */
  public void addSource() {
    
    // Specify the data information
    DataInfo di = new DataInfo(
      "Random source "+size(), // Label of the data source : what is displayed
      "random"+size(),  // Id of the data source : must be unique. Also used by the source providers (see the random plugin) 
      "random values", // A comment, optional
      "unit" // this data source unit, optional 
    );
    
    // Create the source, with data type double
    createDataSource(di, ValueProvider.DoubleProvider);
    // Wrap a buffer around it so we see past values too
    try {
      bufferize(size() -1, new DelayedBuffer(ValueProvider.DoubleProvider, 100));
    } catch (UnsupportedOperation e) {
      e.printStackTrace();
    }
  }
  
//  -----------------------------------------------------------------------
//  Data Source Collection methods
//  -----------------------------------------------------------------------
  /**
   * If a source is sorted, this can lead to some optimization.
   */
  public int sortedOrder(int i) {
    if (i==0) 
      return 1; // time is in ascending order
    
    return super.sortedOrder(i);
  }
  /**
   * Returns information about the collection itself
   */
  public DataInfo getInformation() {
    return new DataInfo(
      "Random Collection",    // The name of this data source collection, as appears in the source pane 
      "RandomCollection"   // A unique ID. This example supposes there is only one instance of this object
    );
  }
//  -----------------------------------------------------------------------
//  Interface used by the Swing Timer .
//  -----------------------------------------------------------------------
  
  /**
   *  This method is called by the timer each time it triggers (that is, every half second for us)
   */
  public void actionPerformed(ActionEvent e) {
    
    // Automatically call step() to produce new data.
    // It would be wise to synchronize the step method too... but let's keep this simple. 
    step();
  }
// -----------------------------------------------------------------------
// ContextualActionProvide interface.
// This adds a contextual popup menu when 
//  right-clicking on the collection in the source pane
// -----------------------------------------------------------------------
  
  
   /**
  * Return the list of possible actions
  * The contect information may be used, or not.
  * 
  * In the case of a popup menu in the source pane, the context is SOURCELIST_CONTEXT
  * @param x Coordinate, mouse position in the same unit as contains(x,y)
  * @param y Coordinate, mouse position in the same unit as contains(x,y)
  * @param o Object the actions should work on. Possibly null => default or all actions
  * @param context one of the context defined in the ContextualActionProvider class
  * @return The list of possible actions, possibly null or an empty array
  */
  public String[] getActions(double x, double y, Object o, int context) {
    if (context!=SOURCELIST_CONTEXT) return null;
    // If the timer is running, we can stop it.
    // It would be possible to add a source while running, with synchronize.
    // See DynamicDataSourceCollection.createDataSource(...) for details. 
    // Now, let's keep this example simple, shall we?
    if (timer.isRunning()) return new String[] { "Stop" };
    // Otherwise, we can either start the timer or add values step by step
    // - start, to start adding values automatically,
    // - step, to add only one value
    // Removing or adding sources is possible too, but don't remove our time source
    if (size()>1) return new String[] { "Start", "Step", "Add Source", "Remove Source" };
    else return new String[] { "Start", "Step", "Add Source" };
  }
  /**
   * Do one of the actions previously declared by getAction.
   * 
   * @param x Coordinate, for example mouse position
   * @param y Coordinate, for example mouse position
   * @param o Object the action should work on.
   * @param action An action returned by a previous getActions call with the same x, y, o parameters
   *               It may be null, in which case the default action is requested for this x,y,o.
   * @return true if the action could be performed
   */
  public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
    if (action.equals("Start")) {
      start();
    }
    
    if (action.equals("Step")) {
      step();
    }
    if (action.equals("Stop")) {
      stop();
    }
    if (action.equals("Add Source")) {
      addSource();
    }
    if (action.equals("Remove Source")) {
        this.removeDataSource((DataSource)lastElement()); // uses vector API
    }
    
    return false;
  }
  /**
   * Returns true if, and only if, it is possible to do the action right now
   * @param x Coordinate, for example mouse position
   * @param y Coordinate, for example mouse position
   * @param o Object the action should work on.
   * @param action An action returned by a previous getActions call with the same x, y, o parameters
   *               It may be null, in which case the default action is requested for this x,y,o.
  * @param context one of the context defined in the ContextualActionProvider class
   * @return true if the action can be performed
   */
  public boolean canDoAction(double x, double y, Object o, String action, int context) {
    
    // This example object is always ready to perform the actions it declared
    return true;
  }
}