/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © 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.
 */

import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.util.IlvImageUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.io.IOException;

/**
 * A base class for "running" a diagram, that is, dynamically changing
 * properties of a diagram's nodes and/or links to simulate a real-time
 * data feed.
 */ 
public abstract class DiagramRunner extends Timer implements ActionListener
{
  // The diagram component that we want to animate.
  //
  IlvDiagrammer diagrammer;
  
  private boolean looping;
  
  private static final String runnerProperty = "_IlvDiagrammer_DiagramRunner";

  private static final String loopProperty = "_IlvDiagrammer_DiagramRunner_Loop";
  
  /**
   * Creates a new diagram runner.
   * 
   * @param delay The delay between two animation steps.
   * @param diagrammer The diagrammer to be animated.
   */ 
  public DiagramRunner(IlvDiagrammer diagrammer, int delay)
  {
    super(delay, null);
    setInitialDelay(0);
    addActionListener(this);
    this.diagrammer = diagrammer;
    diagrammer.putClientProperty(runnerProperty, this);
    diagrammer.getEngine().applyStyle(this, "DiagramRunner", null);
    if(Boolean.TRUE.equals(diagrammer.getClientProperty(loopProperty))){
      setLooping(true);
    }
    diagrammer.putClientProperty(loopProperty, null);
  }
  
  /**
   * Returns the diagram runner attached to an IlvDiagrammer object.
   * @param diagrammer The diagram component.
   */ 
  public static DiagramRunner getRunner(IlvDiagrammer diagrammer)
  {
    return (DiagramRunner) diagrammer.getClientProperty(runnerProperty);
  }

  /**
   * Returns true if the animation loops continuously.
   */ 
  public boolean isLooping()
  {
    return looping;
  }

  /**
   * If looping is true, the animation loops continuously.
   */ 
  public void setLooping(boolean looping)
  {
    this.looping = looping;
  }

  /**
   * Calls the "step" method inside an adjustment sequence.
   */ 
  public void actionPerformed(ActionEvent e)
  {
    diagrammer.setAdjusting(true);
    step();
    diagrammer.setAdjusting(false);
  }
  
  /**
   * Called on all objects when the animation is started.
   */ 
  protected abstract void init(Object object);
  
  /**
   * This method is called at every step of the animation.
   */ 
  protected abstract void step();

  public void start()
  {
    super.start();
    diagrammer.setAdjusting(true);
    Iterator it = diagrammer.getAllObjects();
    while (it.hasNext()) {
      init(it.next());
    }
    diagrammer.setAdjusting(false);
  }

  public void stop()
  {
    super.stop();
    if(looping){
      setInitialDelay(getDelay());
      start();
    } else {
      IlvDiagrammerAction.updateActions(diagrammer.getRootPane(), diagrammer);
    }
  }

  /**
   * A subclass of 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;
    
    public Sequence(IlvDiagrammer diagrammer, int delay)
    {
      super(diagrammer, delay);
    }

    /**
     * Calls the "next" method to determine which object to modify next.
     * Then calls "leave" on the previous object, and "enter" on the new
     * object.
     */ 
    protected void step()
    {
      Object next = next(current);
      if(current != null){
        leave(current);
      }
      if(next != null){
        current = next;
        enter(current);
      } else {
        stop();
      }
    }

    public void start()
    {
      current = null;
      super.start();
    }

    public void stop()
    {
      super.stop();
      if(current != null){
        leave(current);
        current = null;
      }
    }
    
    /**
     * Returns the next object to animate. If this method returns null,
     * the animation stops.
     * 
     * @param previous The current object.
     */ 
    protected abstract Object next(Object previous);
    
    /**
     * Called at each animation step on the previous current object.
     * @param previous The previous current object.
     */ 
    protected abstract void leave(Object previous);
    
    /**
     * Called at each animation step on the new current object.
     * @param next The new current object.
     */ 
    protected abstract void enter(Object next);
  }
  
  /**
   * Bean info class
   */
  public class SequenceBeanInfo extends SimpleBeanInfo {

    /**
     * Constructor
     */
    public SequenceBeanInfo() {
    }
  }

  
  public static class Flow extends Sequence
  {
    public Flow(IlvDiagrammer diagrammer, int delay)
    {
      super(diagrammer, delay);
    }

    /**
     * Returns the next object, following the flow of the diagram:
     * - The first object is the one that has no incoming links.
     * - The next object of a link is its destination node.
     * - When a node has several outgoing links, one of the links is
     *   chosen randomly, otherwise the next object is the outgoing link.
     * 
     * @param previous The current object.
     */ 
    protected Object next(Object previous)
    {
      if(previous == null){
        Iterator selection = diagrammer.getSelectedObjects();
        if(selection.hasNext()){
          return selection.next();
        } else {
          Iterator objs = diagrammer.getAllObjects();
          while (objs.hasNext()) {
            Object o = objs.next();
            if(getLinks(o, false).size() == 0){
              return o;
            }
          }
          objs = diagrammer.getAllObjects();
          if(objs.hasNext())
            return objs.next();
          else
            return null;
        }
      } else if(diagrammer.isLink(previous)){
        return diagrammer.getTargetNode(previous);
      } else {
        ArrayList links = getLinks(previous, true);
        if(links.size() == 0){
          return null;
        } else if(links.size() == 1){
          return links.get(0);
        } else {
          // See if there is a "preferred" path.
          //
          for (int i = 0; i < links.size(); i++) {
            Object link = links.get(i);
            if(diagrammer.getObjectProperty(link, "sdm:preferred") != null){
              return link;
            }
          }
          // Otherwise choose randomly.
          //
          return links.get((int)Math.round(Math.random() * (links.size()-1)));
        }
      }
    }
    
    private ArrayList getLinks(Object node, boolean from)
    {
      IlvSDMEngine engine = diagrammer.getEngine();
      IlvGrapher grapher = engine.getGrapher();
      IlvGraphic nodeGraphic = engine.getGraphic(node, false);
      IlvGraphicEnumeration linksEnum =
              from ?
              grapher.getLinksFrom(nodeGraphic) :
              grapher.getLinksTo(nodeGraphic);
      ArrayList links = new ArrayList();
      while(linksEnum.hasMoreElements()){
        links.add(engine.getObject(linksEnum.nextElement()));
      }
      return links;
    }

    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", "");
    }
    
    /**
     * Selects the next object.
     */ 
    protected void enter(Object next)
    {
      diagrammer.setSelected(next, true);
      diagrammer.setObjectProperty(next, "status", "active");
      diagrammer.scrollToObject(next);
    }

    /**
     * Deselects the previous object.
     */ 
    protected void leave(Object previous)
    {
      diagrammer.setSelected(previous, false);
      diagrammer.setObjectProperty(previous, "status", "done");
    }
  }
  
  /**
   * Bean info class
   */
  public class FlowBeanInfo extends SimpleBeanInfo {

    /**
     * Constructor
     */
    public FlowBeanInfo() {
    }
  }

  
  /**
   * An action that starts/stops a diagram runner.
   */ 
  public static abstract class Run extends IlvDiagrammerAction.ToggleAction
  {
    public Run()
    {
      super("Run", ResourceBundle.getBundle("runner"));
    }
    
    protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception
    {
      return diagrammer != null &&
              DiagramRunner.getRunner(diagrammer) != null &&
              DiagramRunner.getRunner(diagrammer).isRunning();
    }
    
    protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception
    {
      if(diagrammer != null){
        DiagramRunner runner = DiagramRunner.getRunner(diagrammer);
        if(selected){
          if(runner == null){
            runner = createRunner(diagrammer);
          }
          runner.start();
        } else if(runner != null){
          boolean oldLooping = runner.isLooping();
          runner.setLooping(false);
          runner.stop();
          runner.setLooping(oldLooping);
          setIcon(Action.SMALL_ICON);
        }
      }
    }

    protected void setSelected(boolean selected)
    {
      super.setSelected(selected);
      if(selected){
        setIcon("StopIcon");
      } else {
        setIcon(Action.SMALL_ICON);
      }
    }

    private void setIcon(String key)
    {
      try {
        String s = getResourceBundle().getString(getPrefix() + "." + key);
        Image image = null;
        image = IlvImageUtil.getImageFromFile(getClass(), s);
        putValue(Action.SMALL_ICON, new ImageIcon(image));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception
    {
      return true;
    }
    
    protected abstract DiagramRunner createRunner(IlvDiagrammer diagrammer);
  }
  
 
  /**
   * An action that toggle continuous looping of the animation.
   */ 
  public static class Loop extends IlvDiagrammerAction.ToggleAction
  {
    public Loop()
    {
      super("Loop", ResourceBundle.getBundle("runner"));
    }
    
    protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception
    {
      return diagrammer != null &&
              ((DiagramRunner.getRunner(diagrammer) != null &&
              DiagramRunner.getRunner(diagrammer).isLooping()) ||
              Boolean.TRUE.equals(diagrammer.getClientProperty(loopProperty)));
    }
    
    protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception
    {
      if(diagrammer != null){
        DiagramRunner runner = DiagramRunner.getRunner(diagrammer);
        if(runner != null){
          runner.setLooping(selected);
        } else {
          diagrammer.putClientProperty(loopProperty, new Boolean(selected));
        }
      }
    }
    
    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception
    {
      return true;
    }
  }
}