/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

package demo.runner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

import javax.servlet.http.HttpServletRequest;

import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvManagerView;
import ilog.views.IlvRect;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.sdm.IlvSDMEngine;

/**
 * A base class to <i>run</i> a diagram, which means to simulate real-time data
 * feed by dynamically changing properties of the nodes and links of an
 * {@link IlvDiagrammer}.
 */
public abstract class DiagramRunner {
  // The diagram component that contains a data model to be modified
  protected IlvDiagrammer _diagrammer;

  // Whether the simulation should run constantly, or if it should stop
  // at the end of the diagram
  private boolean _looping;

  // Update interval between each simulation step
  private int _period = 10000;

  // The Timer scheduling the simulation steps on a background thread
  private Timer _timer;

  // The TimerTask representing the simulation steps, executed
  // repeatedly by the Timer
  private TimerTask _task;

  // Property name to store the DiagramRunner inside the IlvDiagrammer
  private static final String runnerProperty = "_IlvDiagrammer_DiagramRunner";

  /**
   * Constructor.
   * 
   * @param diagrammer
   *          The diagrammer to be <i>run</i>.
   */
  public DiagramRunner(IlvDiagrammer diagrammer) {
    // Keep a reference to the IlvDiagrammer
    _diagrammer = diagrammer;
    // Add this DiagramRunner into the IlvDiagrammer as a property
    _diagrammer.putClientProperty(runnerProperty, this);
    // Apply the StyleSheet (CSS) to this IlvDiagrammer
    diagrammer.getEngine().applyStyle(this, "DiagramRunner", null);
  }

  /**
   * Returns the {@link DiagramRunner} attached to the given
   * {@link IlvDiagrammer} instance.
   * 
   * @param diagrammer
   *          The {@link IlvDiagrammer} instance.
   */
  public static DiagramRunner getRunner(IlvDiagrammer diagrammer) {
    // Tries to retrieve the DiagramRunner from the IlvDiagrammer (as a
    // property)
    return (DiagramRunner) diagrammer.getClientProperty(runnerProperty);
  }

  /**
   * Returns whether the simulation should repeat continuously or not.
   */
  public boolean isLooping() {
    return _looping;
  }

  /**
   * Defines whether the simulation should repeat continuously or not.
   * 
   * @param looping
   *          If <code>true</code>, the simulation runs continuously.
   */
  public void setLooping(boolean looping) {
    _looping = looping;
  }

  /**
   * Returns the time interval between each simulation step, in milliseconds.
   */
  int getPeriod() {
    return _period;
  }

  /**
   * Defines the time interval between each simulation step, in milliseconds.
   * 
   * @param period
   *          The new time interval.
   */
  void setPeriod(int period) {
    _period = period;
  }

  /**
   * Abstract method called on all objects when the simulation is started.
   * 
   * @param object
   *          The object being initialized
   */
  protected abstract void init(Object object);

  /**
   * Abstract method to return the manager bounding box according to the current
   * simulation state.
   * 
   * @param req
   *          The client request
   * @param view
   *          The manager view associated with the {@link IlvDiagrammer}
   * 
   * @return The bounding box corresponding to the current simulation state.
   */
  protected abstract IlvRect getManagerBBox(HttpServletRequest req, IlvManagerView view);

  /**
   * Abstratct method that is called at every step of the simulation.
   */
  protected abstract void step();

  /**
   * Returns a new <code>TimerTask</code>, which invokes {@link #step()} when
   * executed.
   */
  private TimerTask newTask() {
    return new TimerTask() {
      Override
      public void run() {
        step();
      }
    };
  }

  /**
   * Restarts the simulation.
   */
  public void restart() {
    if (null != _task) {
      try {
        // Tries to cancel the current task
        _task.cancel();
      } catch (Exception e) {
        System.err.println(e.toString());
      }

      // Instantiates new Timer and TimerTask
      _timer = new Timer();
      _task = newTask();

      // Schedule TimerTask for execution
      try {
        System.err.println("schedule runnable " + getPeriod());
        _timer.schedule(_task, 0, getPeriod());
      } catch (Exception e) {
        System.err.println(e.toString());
      }
    }
  }

  /**
   * Starts the simulation
   */
  public void start() {
    // Invoke 'init()' method for every object
    _diagrammer.setAdjusting(true);
    Iterator<?> it = _diagrammer.getAllObjects();
    while (it.hasNext()) {
      init(it.next());
    }
    _diagrammer.setAdjusting(false);

    // Instantiates new Timer and TimerTask
    _timer = new Timer();
    _task = newTask();

    // Schedule TimerTask for execution
    try {
      System.err.println("schedule runnable " + getPeriod());
      _timer.schedule(_task, 0, getPeriod());
    } catch (Exception e) {
      System.err.println(e.toString());
    }
  }

  /**
   * Stops the simulation
   */
  public void stop() {
    if (null != _timer) {
      // Cancels Timer
      _timer.cancel();
      _task.cancel();
    }
    _task = null;
    _timer = null;
  }

  /**
   * A subclass of {@link DiagramRunner} that modifies some properties of one
   * object after another.
   */
  public static abstract class Sequence extends DiagramRunner {
    // The current object, that is, the last object that was modified.
    private Object _current;

    /**
     * Constructor
     * 
     * @param diagrammer
     *          The {@link IlvDiagrammer} to be <i>run</i>
     */
    public Sequence(IlvDiagrammer diagrammer) {
      super(diagrammer);
    }

    /**
     * Method to return the manager bounding box around the current object being
     * updated.
     * 
     * @param req
     *          The client request
     * @param view
     *          The manager view associated with the {@link IlvDiagrammer}
     * 
     * @return The bounding box corresponding to the current simulation state.
     */
    Override
    protected IlvRect getManagerBBox(HttpServletRequest req, IlvManagerView view) {
      if (null == _current) {
        return null;
      }
      // Get the graphic corresponding to the current object
      IlvGraphic g = _diagrammer.getEngine().getGraphic(_current, false);
      // Get the bounding box of the graphic
      IlvRect rect = new IlvRect(g.boundingBox());
      // Expand the bounding box to two times the largest dimension
      rect.expand(2 * Math.max(rect.width + 5, rect.height + 5));

      return rect;
    }

    /**
     * <p>
     * Calls the {@link #next(Object)} method to determine what is the next
     * object to be modified.
     * </p>
     * 
     * <p>
     * Then calls {@link #leave(Object)} on the current object.
     * </p>
     * 
     * <p>
     * And finally calls {@link #enter(Object)} on the new object.
     * </p>
     */
    Override
    protected void step() {
      Object next = next(_current);
      if (null != _current) {
        leave(_current);
      }
      if (null != next) {
        _current = next;
        enter(_current);
      } else {
        stop();
      }
    }

    /**
     * Overrides method to clean the current object.
     */
    Override
    public void start() {
      _current = null;
      super.start();
    }

    /**
     * Overrides method to clean the current object.
     */
    Override
    public void stop() {
      super.stop();
      if (null != _current) {
        leave(_current);
        _current = null;
      }
      if (isLooping()) {
        start();
      }
    }

    /**
     * Abstract method to compute the next object to be updated in the
     * simulation. The simulation stops if this method returns
     * <code>null</code>.
     * 
     * @param current
     *          The current object.
     * 
     * @return The next object to be updated in the simulation, or
     *         <code>null</code>.
     */
    protected abstract Object next(Object current);

    /**
     * Abstract method called at each simulation step to indicate that the
     * updates on the current object is about to be completed.
     * 
     * @param current
     *          The current object being finalized.
     */
    protected abstract void leave(Object current);

    /**
     * Abstract method called at each animation step on the next object to be
     * updated by the simulation.
     * 
     * @param next
     *          The object that will become the new current object
     */
    protected abstract void enter(Object next);
  }

  /**
   * Implementation of {@link Sequence} that follows a flow diagram in an
   * {@link IlvDiagrammer}.
   */
  public static class Flow extends Sequence {
    /**
     * Constructor.
     * 
     * @param diagrammer
     *          The diagrammer to be <i>run</i>.
     */
    public Flow(IlvDiagrammer diagrammer) {
      super(diagrammer);
    }

    /**
     * <p>
     * Returns the next object, following the flow of the diagram:
     * </p>
     * <ul>
     * <li>The first object is the one that has no incoming links.</li>
     * <li>The next object of a link is its destination node.</li>
     * <li>When a node has several outgoing links, one of the links is chosen
     * randomly, otherwise the next object is the outgoing link.</li>
     * </ul>
     * 
     * @param current
     *          The current object.
     */
    Override
    protected Object next(Object current) {
      if (null == current) {
        // There is no 'current' object, tries to start from the
        // currently selected object
        Iterator<?> selection = _diagrammer.getSelectedObjects();
        if (selection.hasNext()) {
          return selection.next();
        }
        // No object is selected, try to find an object with
        // no incoming links
        Iterator<?> objs = _diagrammer.getAllObjects();
        while (objs.hasNext()) {
          Object o = objs.next();
          if (getLinks(o, false).size() == 0) {
            return o;
          }
        }
        // Fallback option, return any object in the diagram
        objs = _diagrammer.getAllObjects();
        if (objs.hasNext()) {
          return objs.next();
        }
        // No objects in the diagram
        return null;
      } else if (_diagrammer.isLink(current)) {
        // The current object is a link, return its 'to' endpoint
        return _diagrammer.getTargetNode(current);
      } else {
        // The current object is a node, try to get a 'from' link
        ArrayList<?> links = getLinks(current, true);
        if (links.size() == 0) {
          // No 'from' links, the simulation is over
          return null;
        } else if (links.size() == 1) {
          // Just one 'from' link, use it
          return links.get(0);
        } else {
          // Multiple 'from' links, see if there is a "preferred" path.
          for (Object link : links) {
            if (_diagrammer.getObjectProperty(link, "sdm:preferred") != null) {
              return link;
            }
          }
          // Otherwise choose randomly.
          return links.get((int) Math.round(Math.random() * (links.size() - 1)));
        }
      }
    }

    /**
     * Method invoked on every object in the model when the simulation starts
     */
    /**
     * <p>
     * Method called on all objects when the simulation is started.
     * </p>
     * 
     * <p>
     * Ensures that the node is <b>not</b> selected, and sets the object
     * property <i>status</i> to <i>empty</i>.
     * 
     * @param object
     *          The object being initialized
     */
    Override
    protected void init(Object object) {
      _diagrammer.setSelected(object, false);
      // Simply set the property value to empty
      // This will ensure that the active nor the
      // done state is represented
      _diagrammer.setObjectProperty(object, "status", "");
    }

    /**
     * <p>
     * Method called at each animation step on the next object to be updated by
     * the simulation.
     * </p>
     * 
     * <p>
     * Ensure that the object is selected, then set the object property
     * <i>status</i> to <i>active</i>.
     * </p>
     * 
     * @param next
     *          The object that will become the new current object
     */
    Override
    protected void enter(Object next) {
      _diagrammer.setSelected(next, true);
      _diagrammer.setObjectProperty(next, "status", "active");
    }

    /**
     * <p>
     * Method called at each simulation step to indicate that the updates on the
     * current object is about to be completed.
     * </p>
     * 
     * <p>
     * Ensures that the object is not selected, then set the object property
     * <i>status</i> to <i>done</i>.
     * </p>
     * 
     * @param current
     *          The current object being finalized.
     */
    Override
    protected void leave(Object previous) {
      _diagrammer.setSelected(previous, false);
      _diagrammer.setObjectProperty(previous, "status", "done");
    }

    /**
     * Private method to retrieve either the links ending on a node (<i>to</i>)
     * or the links starting on a node (<i>from</i>).
     * 
     * @param node
     *          The node connected to links
     * @param from
     *          Whether the <i>from</i> or <i>to</i> links should be returned
     * 
     * @return A list of links either starting (<code>from</code> parameter is
     *         <code>true</code>) or ending in the given node.
     */
    private ArrayList<?> getLinks(Object node, boolean from) {
      // Get the SDM engine from the IlvDiagrammer
      IlvSDMEngine engine = _diagrammer.getEngine();
      // Get the graphic representing the given node
      IlvGraphic nodeGraphic = engine.getGraphic(node, false);
      // Get the grapher containing all objects
      IlvGrapher grapher = engine.getGrapher();
      // Gets either the 'from' or 'to' links connected to the node
      IlvGraphicEnumeration linksEnum = from ? grapher.getLinksFrom(nodeGraphic) : grapher.getLinksTo(nodeGraphic);
      // Converts the enumeration into an ArrayList
      ArrayList<Object> links = new ArrayList<Object>();
      while (linksEnum.hasMoreElements()) {
        links.add(engine.getObject(linksEnum.nextElement()));
      }
      return links;
    }
  }
}