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

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ResourceBundle;

import ilog.views.IlvRect;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.diagrammer.application.IlvDiagrammerFrame;
import ilog.views.diagrammer.application.IlvDiagrammerToolBar;
import ilog.views.sdm.IlvSDMModel;
import ilog.views.sdm.modeltools.IlvContentController;
import ilog.views.sdm.modeltools.IlvContentHandler;
import ilog.views.sdm.modeltools.IlvVisibleAreaListener;
import ilog.views.util.IlvProductUtil;

/**
 * This example shows how to use content on demand tool.
 * 
 * @since JViews 8.0
 * @proofread
 */
public class ContentDemo extends IlvDiagrammerFrame {

  ////////////////////////////////////////////////////
  // The following fields are interesting to modify,

  /** Number of nodes */
  public int _nodeNumber = 100; // best between 100 and 10 000
  /** Offset between nodes, in the grid */
  public long _delta = 40;
  /** Size of the cache */
  public int _cacheSize = _nodeNumber / 2; // 0 is also an interesting value
  /**
   * Show nodes in cache. This is less realistic, slower, but it shows how the
   * cache is working.
   */
  public boolean _showCachedNodes = true;

  ////////////////////////////////////////////////////

  private static final String CODstatus = "detailsOnDemand";
  private static final String LOCKED = new String("locked");
  private static final String LOADED = new String("loaded");
  private static final String UNLOADED = new String("unloaded");
  private static final String CONTENT = "content";
  private IlvContentController _controller;

  // entry point
  public static void main(final String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        new ContentDemo().init(args);
      }
    });
  }

  // constructor
  public ContentDemo() {
    super(new String[] { "-data", "data/grid.idpr", "-simple" });

    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Perforce JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Diagrammer_Deployment);
  }

  // init GUI
  Override
  public void init(Container contentPane) {
    super.init(contentPane);

    ResourceBundle rb;
    rb = ResourceBundle.getBundle("main");

    // add actions
    IlvDiagrammerToolBar tb = getViewToolBar();
    tb.addAction(new MoveAreaAction(rb));

    IlvDiagrammerToolBar toolbar = new IlvDiagrammerToolBar();
    toolbar.addAction(new Lock(rb));
    toolbar.addAction(new Load(rb));
    toolbar.addAction(new Unload(rb));
    contentPane.add(toolbar, BorderLayout.NORTH);
  }

  // GUI is ready, init application
  Override
  public void ready() {
    super.ready();
    // redo the diagram
    populateDiagram(_nodeNumber);
    final IlvDiagrammer diagrammer = getDiagrammers()[0];

    // initialize the content controler
    _controller = new IlvContentController();
    _controller.setCacheSize(_cacheSize);
    _controller.setSDMEngine(diagrammer.getEngine());
    _controller.setContentHandler(new IlvContentHandler() {

      // callback for loading content. Could be done in a separate thread, too.
      Override
      public void loadContent(IlvContentController source, Object[] objects) {
        // prevent too much notifications
        diagrammer.setAdjusting(true);
        // loop over objects to load
        for (int i = 0; i < objects.length; i++) {
          loadObject(objects[i], true);
        }
        diagrammer.setAdjusting(false);
      }

      // callback for unloading content
      Override
      public void unloadContent(IlvContentController source, Object[] objects) {
        diagrammer.setAdjusting(true);
        for (int i = 0; i < objects.length; i++) {
          loadObject(objects[i], false);
        }
        diagrammer.setAdjusting(false);
      }
    });

    // install view listener to lock objects in visible area.
    diagrammer.getView().fitTransformerToContent();
    IlvVisibleAreaListener val = new IlvVisibleAreaListener(_controller) {
      // here we subclass only to check nodes in cache
      Override
      public int requestAreaToLock(IlvRect area) {
        int result = super.requestAreaToLock(area);
        // check whether some nodes goes to cache
        if (_showCachedNodes) {
          updateStates();
        }
        return result;
      }

    };
    val.installListener(_controller.getSDMEngine().getReferenceView());
  }

  ///////////////////////////////

  // make "pop" different nodes, in a grid
  private void populateDiagram(int pop) {
    IlvDiagrammer d = getDiagrammers()[0];
    d.setAdjusting(true);
    IlvSDMModel model = d.getEngine().getModel();
    model.clear();
    long width = (long) Math.sqrt(pop) * _delta;
    long x = 0;
    long y = 0;
    for (int i = 0; i < pop; i++) {
      Object node = model.createNode("form");
      model.setObjectProperty(node, "name", Integer.valueOf(i));
      model.setObjectProperty(node, "x", Long.valueOf(x));
      model.setObjectProperty(node, "y", Long.valueOf(y));
      x += _delta;
      if (x >= width) {
        x = 0;
        y += _delta;
      }
      // all nodes are unloaded
      model.setObjectProperty(node, CODstatus, UNLOADED);
      model.addObject(node, null, null);
    }
    d.setAdjusting(false);
  }

  // callback from lock/load/unload actions
  private void workOnSelection(int action) {
    IlvDiagrammer d = getDiagrammers()[0];
    // loop on selected objects
    Iterator<?> selected = d.getSelectedObjects();
    _controller.processObjects(selected, action);
    // check whether some nodes goes to cache
    if (_showCachedNodes) {
      updateStates();
    }
  }

  /**
   * Callback from RectangleInteractor
   * 
   * @param area
   *          the area to lock
   */
  void lockArea(IlvRect area) {
    _controller.lockArea(area, IlvContentController.OVERRIDE);
    // check whether some nodes goes to cache
    if (_showCachedNodes) {
      updateStates();
    }
  }

  // the exact status of objects, including in cache. This is slow, because
  // we need to check state of all objects.
  private void updateStates() {
    getDiagrammers()[0].setAdjusting(true);
    IlvSDMModel model = getDiagrammers()[0].getEngine().getModel();
    // loop over all objects
    Enumeration<?> e = getDiagrammers()[0].getEngine().getAllObjects();
    while (e.hasMoreElements()) {
      Object node = e.nextElement();
      String expected = statToString(_controller.getObjectStatus(node));
      if (model.getObjectProperty(node, CODstatus) != expected) { // == is ok,
                                                                  // here
        model.setObjectProperty(node, CODstatus, expected);
      }
    }
    getDiagrammers()[0].setAdjusting(false);
  }

  /**
   * Loads or unloads an object
   * 
   * @param node
   *          the object
   * @param load
   *          the action
   */
  private void loadObject(Object node, boolean load) {
    IlvSDMModel model = getDiagrammers()[0].getEngine().getModel();
    if (load) {
      // here we simulate the loading of node contents
      // it can be also interesting to put a sleep() command here, or loading a
      // huge value.
      model.setObjectProperty(node, CONTENT, "content should be more complex than this string");
    } else {
      // unload values
      model.setObjectProperty(node, CONTENT, null);
    }
    // adjust state. It's use only to have an accurate visual feedback in the
    // CSS
    model.setObjectProperty(node, CODstatus, statToString(_controller.getObjectStatus(node)));
  }

  /**
   * Converts content status to a string that is simpler to match in the CSS.
   * 
   * @param objectStatus
   *          the loaded state
   * @return a string
   */
  private String statToString(int objectStatus) {
    switch (objectStatus) {
    case IlvContentController.LOCK:
      return LOCKED;
    case IlvContentController.LOAD:
      return LOADED;
    case IlvContentController.UNLOAD:
      return UNLOADED;
    }
    return null;
  }

  /////////////////////////////////
  // inner classes

  /**
   * The action that locks selected objects.
   */
  private class Lock extends IlvDiagrammerAction {
    public Lock(ResourceBundle rb) {
      super("Lock", rb);
    }

    Override
    public void perform(ActionEvent e, IlvDiagrammer source) {
      workOnSelection(IlvContentController.LOCK);
    }

    Override
    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
      return true;
    }
  }

  /**
   * The action that load in cache selected objects.
   */
  private class Load extends IlvDiagrammerAction {
    public Load(ResourceBundle rb) {
      super("Load", rb);
    }

    Override
    public void perform(ActionEvent e, IlvDiagrammer source) {
      workOnSelection(IlvContentController.LOAD);
    }

    Override
    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
      return true;
    }
  }

  /**
   * The action that unloads selected objects.
   */
  private class Unload extends IlvDiagrammerAction {
    public Unload(ResourceBundle rb) {
      super("Unload", rb);
    }

    Override
    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
      return true;
    }

    Override
    public void perform(ActionEvent e, IlvDiagrammer source) {
      workOnSelection(IlvContentController.UNLOAD);
    }
  }

  /**
   * An action that controls the content on demand through a visible area.
   */
  private class MoveAreaAction extends IlvDiagrammerAction.ToggleAction {

    /**
     * @param bundle
     */
    protected MoveAreaAction(ResourceBundle bundle) {
      super("MoveArea", bundle);
    }

    /*
     * (non-Javadoc)
     * 
     * @see ilog.views.diagrammer.application.IlvDiagrammerAction.ToggleAction#
     * isSelected(ilog.views.diagrammer.IlvDiagrammer)
     */
    Override
    protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception {
      return diagrammer != null && diagrammer.getView().getInteractor() instanceof RectangleInteractor;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ilog.views.diagrammer.application.IlvDiagrammerAction.ToggleAction#
     * setSelected(ilog.views.diagrammer.IlvDiagrammer, boolean)
     */
    Override
    protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception {
      if (selected) {
        diagrammer.getView().pushInteractor(new RectangleInteractor(ContentDemo.this));
      } else {
        diagrammer.getView().popInteractor();
      }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * ilog.views.diagrammer.application.IlvDiagrammerAction#isEnabled(ilog.
     * views.diagrammer.IlvDiagrammer)
     */
    Override
    protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
      return diagrammer != null;
    }

  }
}