/*
 * 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.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.util.Comparator;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;

import ilog.views.IlvAccelerator;
import ilog.views.IlvDefaultManagerFrame;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.accelerator.IlvCycleSelectAccelerator;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.swing.IlvJComboBox;

/**
 * This is a very simple application that shows how to implement
 * a selection mechanism that uses the keyboard.
 */
SuppressWarnings("serial")
public class KeyboardSelApp extends JRootPane {

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

  // --------------------------------------------------------------------

  IlvGrapher grapher;
  IlvManagerView mgrview;
  IlvZoomableLabel node;
  boolean animate = true;
  IlvCycleSelectAccelerator[] accelerators;

  // comparator for the traversal order: x has higher priority
  Comparator<IlvGraphic> xThenY = new Comparator<IlvGraphic>() {
    Override
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvManager ma = (IlvManager) a.getGraphicBag();
      IlvManager mb = (IlvManager) b.getGraphicBag();
      IlvTransformer ta = ma.getTopLevelTransformer();
      IlvTransformer tb = mb.getTopLevelTransformer();
      IlvRect ra = a.boundingBox(ta);
      IlvRect rb = b.boundingBox(tb);
      if (ra.x < rb.x)
        return -1;
      if (ra.x > rb.x)
        return 1;
      if (ra.width < rb.width)
        return -1;
      if (ra.width > rb.width)
        return 1;
      if (ra.y < rb.y)
        return -1;
      if (ra.y > rb.y)
        return 1;
      if (ra.height < rb.height)
        return -1;
      if (ra.height > rb.height)
        return 1;
      return 0;
    }
  };

  // comparator for the traversal order: y has higher priority
  Comparator<IlvGraphic> yThenX = new Comparator<IlvGraphic>() {
    Override
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvManager ma = (IlvManager) a.getGraphicBag();
      IlvManager mb = (IlvManager) b.getGraphicBag();
      IlvTransformer ta = ma.getTopLevelTransformer();
      IlvTransformer tb = mb.getTopLevelTransformer();
      IlvRect ra = a.boundingBox(ta);
      IlvRect rb = b.boundingBox(tb);
      if (ra.y < rb.y)
        return -1;
      if (ra.y > rb.y)
        return 1;
      if (ra.height < rb.height)
        return -1;
      if (ra.height > rb.height)
        return 1;
      if (ra.x < rb.x)
        return -1;
      if (ra.x > rb.x)
        return 1;
      if (ra.width < rb.width)
        return -1;
      if (ra.width > rb.width)
        return 1;
      return 0;
    }
  };

  // comparator for the traversal order: nesting has highest priority, then x
  Comparator<IlvGraphic> nestedThenXThenY = new Comparator<IlvGraphic>() {
    Override
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvGrapher commonBag = IlvGrapher.getLowestCommonGrapher(a, b);
      if (commonBag != null) {
        while (a.getGraphicBag() != commonBag)
          a = (IlvGraphic) a.getGraphicBag();
        while (b.getGraphicBag() != commonBag)
          b = (IlvGraphic) b.getGraphicBag();
      }
      IlvRect ra = a.boundingBox();
      IlvRect rb = b.boundingBox();
      if (ra.x < rb.x)
        return -1;
      if (ra.x > rb.x)
        return 1;
      if (ra.width > rb.width)
        return -1;
      if (ra.width < rb.width)
        return 1;
      if (ra.y < rb.y)
        return -1;
      if (ra.y > rb.y)
        return 1;
      if (ra.height > rb.height)
        return -1;
      if (ra.height < rb.height)
        return 1;
      return 0;
    }
  };

  // comparator for the traversal order: nesting has highest priority, then y
  Comparator<IlvGraphic> nestedThenYThenX = new Comparator<IlvGraphic>() {
    Override
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvGrapher commonBag = IlvGrapher.getLowestCommonGrapher(a, b);
      if (commonBag != null) {
        while (a.getGraphicBag() != commonBag)
          a = (IlvGraphic) a.getGraphicBag();
        while (b.getGraphicBag() != commonBag)
          b = (IlvGraphic) b.getGraphicBag();
      }
      IlvRect ra = a.boundingBox();
      IlvRect rb = b.boundingBox();
      if (ra.y < rb.y)
        return -1;
      if (ra.y > rb.y)
        return 1;
      if (ra.height > rb.height)
        return -1;
      if (ra.height < rb.height)
        return 1;
      if (ra.x < rb.x)
        return -1;
      if (ra.x > rb.x)
        return 1;
      if (ra.width > rb.width)
        return -1;
      if (ra.width < rb.width)
        return 1;
      return 0;
    }
  };


  /**
   * Initializes the application.
   */
  public void init() {
    // create a grapher
    grapher = new IlvGrapher();

    // create a manager view
    mgrview = new IlvManagerView(grapher);

    // create the scroll manager view
    IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview);

    // Some settings on the manager view and on the scroll manager view
    mgrview.setAntialiasing(true);
    mgrview.setKeepingAspectRatio(true);
    mgrview.setBackground(Color.white);
    mgrview.setForeground(SystemColor.windowText);
    Color xc = SystemColor.windowText;
    xc = new Color(255 - xc.getRed(), 255 - xc.getGreen(), 255 - xc.getBlue());
    mgrview.setDefaultXORColor(xc);
    mgrview.setDefaultGhostColor(SystemColor.windowText);
    mgrview.setZoomFactorRange(0.02, 10.0);

    // Settings parameters for selection handles
    IlvHandlesSelection.defaultHandleColor = Color.blue;
    IlvHandlesSelection.defaultHandleBackgroundColor = Color.blue;
    IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE;

    // Install the select interactor
    mgrview.setInteractor(new IlvSelectInteractor());

    // ensure that the manager view receives the TAB key events
    mgrview.setFocusable(true);
    mgrview.setFocusCycleRoot(true);
    mgrview.setFocusTraversalKeysEnabled(false);

    // add the accelerators
    accelerators = new IlvCycleSelectAccelerator[4];
    IlvCycleSelectAccelerator acc;

    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED, KeyEvent.VK_TAB, 0) {
      Override
      protected boolean endOfSequence(IlvGraphic g) {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
        return true;
      }
    };
    acc.setForward(true);
    accelerators[0] = acc;
    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED, 'F', 0);
    acc.setForward(true);
    accelerators[1] = acc;
    acc =
        new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED, KeyEvent.VK_TAB, KeyEvent.SHIFT_MASK) {
          Override
          protected boolean endOfSequence(IlvGraphic g) {
            KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
            KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
            return true;
          }
        };
    acc.setForward(false);
    accelerators[2] = acc;
    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED, 'B', 0);
    acc.setForward(false);
    accelerators[3] = acc;

    for (int i = 0; i < accelerators.length; i++)
      grapher.addAccelerator(accelerators[i]);

    IlvAccelerator acc2 =
        new IlvAccelerator(KeyEvent.KEY_PRESSED, KeyEvent.VK_TAB, KeyEvent.CTRL_MASK) {
          Override
          protected boolean handleEvent(IlvManagerView view) {
            KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
            return true;
          }
        };
    grapher.addAccelerator(acc2);

    // Now, fill the grapher
    createGraph();

    // create a check box that allows the selection of expanded subgraphs
    JCheckBox expandedSubgraphsCheckBox = new JCheckBox("Subgraphs", true);
    expandedSubgraphsCheckBox.setToolTipText("Allow the selection of expanded subgraphs");
    expandedSubgraphsCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i]
              .setSelectExpandedSubgraphsAllowed(e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows the selection of nodes
    JCheckBox nodesCheckBox = new JCheckBox("Nodes", true);
    nodesCheckBox.setToolTipText("Allow the selection of nodes");
    nodesCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setSelectNodesAllowed(e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows the selection of links
    JCheckBox linksCheckBox = new JCheckBox("Links", false);
    linksCheckBox.setToolTipText("Allow the selection of links");
    linksCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setSelectLinksAllowed(e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows you to descend into subgraphs
    JCheckBox descendCheckBox = new JCheckBox("Descend into subgraphs", true);
    descendCheckBox.setToolTipText("Allow the selection cycle to descend into subgraphs");
    descendCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i]
              .setDescendIntoExpandedSubgraphsAllowed(e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a combination box for the order mode
    SuppressWarnings("unchecked")
    JComboBox<String> orderingBox = new IlvJComboBox();
    orderingBox.addItem("unordered");
    orderingBox.addItem("x then y");
    orderingBox.addItem("y then x");
    orderingBox.addItem("nested then x");
    orderingBox.addItem("nested then y");
    orderingBox.setToolTipText("Select the selection traversal order");
    orderingBox.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String mode = (String) comboBox.getSelectedItem();
        Comparator<IlvGraphic> c = null;
        if ("unordered".equals(mode)) {
          c = null;
        } else if ("x then y".equals(mode)) {
          c = xThenY;
        } else if ("y then x".equals(mode)) {
          c = yThenX;
        } else if ("nested then x".equals(mode)) {
          c = nestedThenXThenY;
        } else if ("nested then y".equals(mode)) {
          c = nestedThenYThenX;
        }
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setComparator(c);
      }
    });

    JPanel aux = new JPanel();
    aux.setLayout(new FlowLayout(FlowLayout.LEADING));
    aux.add(new JLabel("Ordering: "));
    aux.add(orderingBox);

    // create the top panel with the toolbar and the pop-up checkbox
    JPanel topPanel = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEADING));

    final Dimension d1 = (new JLabel("Allow Selection of")).getPreferredSize();

    JPanel leadingPanel = new JPanel() {
      Override
      public Dimension getMinimumSize() {
        return getPreferredSize();
      }

      Override
      public Dimension getPreferredSize() {
        Dimension d0 = super.getPreferredSize();
        Dimension dd = new Dimension(d1);
        dd.width += 40;
        if (d0.width > dd.width)
          dd.width = d0.width;
        if (d0.height > dd.height)
          dd.height = d0.height;
        return dd;
      }
    };

    final Dimension d2 = (new JLabel("Further Options")).getPreferredSize();

    JPanel trailingPanel = new JPanel() {
      Override
      public Dimension getMinimumSize() {
        return getPreferredSize();
      }

      Override
      public Dimension getPreferredSize() {
        Dimension d0 = super.getPreferredSize();
        Dimension dd = new Dimension(d2);
        dd.width += 40;
        if (d0.width > dd.width)
          dd.width = d0.width;
        if (d0.height > dd.height)
          dd.height = d0.height;
        return dd;
      }
    };

    leadingPanel.setBorder(new TitledBorder("Allow Selection of"));
    leadingPanel.setLayout(new GridLayout(3, 1));
    leadingPanel.add(expandedSubgraphsCheckBox);
    leadingPanel.add(nodesCheckBox);
    leadingPanel.add(linksCheckBox);

    trailingPanel.setBorder(new TitledBorder("Further Options"));
    trailingPanel.setLayout(new GridLayout(2, 1));
    trailingPanel.add(descendCheckBox);
    trailingPanel.add(aux);

    topPanel.add(leadingPanel);
    topPanel.add(trailingPanel);

    // put manager view, top panel and bottom panel together
    getContentPane().setLayout(new BorderLayout(0, 0));
    getContentPane().add(scrollManView, BorderLayout.CENTER);
    getContentPane().add(topPanel, BorderLayout.NORTH);

    mgrview.setDoubleBuffering(true);
    mgrview.setTransformer(new IlvTransformer(1, 0, 0, 1, 0, 0));
  }

  /**
   * Creates a sample graph.
   */
  private void createGraph() {
    IlvGraphic[] inner1 = createSubGraph(grapher);
    IlvGraphic sg1 = (IlvGraphic) inner1[0].getGraphicBag();

    IlvGraphic[] inner2 = createSubGraph(grapher);
    IlvGraphic sg2 = (IlvGraphic) inner2[0].getGraphicBag();

    grapher.moveObject(sg1, 220, 20, true);
    grapher.moveObject(sg2, 221, 140, true);

    IlvGraphic[] nodes = new IlvGraphic[7];
    IlvPoint[] positions =
        {new IlvPoint(20, 0), new IlvPoint(90, 0), new IlvPoint(90, 60), new IlvPoint(160, 0),
            new IlvPoint(160, 60), new IlvPoint(160, 120), new IlvPoint(160, 180)};

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    alignVertical(nodes[3], inner1[0]);
    alignVertical(nodes[4], inner1[2]);
    alignVertical(nodes[5], inner2[0]);
    alignVertical(nodes[6], inner2[2]);

    centerVertical(nodes[1], nodes[3], nodes[4]);
    centerVertical(nodes[2], nodes[5], nodes[6]);
    centerVertical(nodes[0], nodes[1], nodes[2]);

    int[] linksFrom = {0, 0, 1, 1, 2, 2, 3, 4, 5, 6};
    int[] linksTo = {1, 2, 3, 4, 5, 6, 7, 9, 11, 13};

    for (int i = 0; i < linksFrom.length; i++) {
      int fi = linksFrom[i];
      IlvGraphic from = nodes[fi];
      int ti = linksTo[i];
      IlvGraphic to;
      if (ti < nodes.length)
        to = nodes[ti];
      else if (ti < nodes.length + inner1.length)
        to = inner1[ti - nodes.length];
      else
        to = inner2[ti - nodes.length - inner1.length];
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }
  }

  /**
   * Centers a node between two nodes in y direction.
   */
  private void centerVertical(IlvGraphic node, IlvGraphic a, IlvGraphic b) {
    IlvRect bbox = a.boundingBox();
    double min = bbox.y;
    bbox = b.boundingBox();
    double max = bbox.y + bbox.height;
    bbox = node.boundingBox();
    grapher.moveObject(node, bbox.x, (max + min - bbox.height) / 2, true);
  }

  /**
   * Aligns two nodes in y direction.
   */
  private void alignVertical(IlvGraphic node, IlvGraphic ref) {
    IlvManager m = (IlvManager) ref.getGraphicBag();
    IlvTransformer t = m.getTopLevelTransformer();
    IlvRect bbox = ref.boundingBox(t);
    double y = bbox.y;
    bbox = node.boundingBox();
    grapher.moveObject(node, bbox.x, y, true);
  }

  /**
   * Creates a double nested subgraph.
   */
  private IlvGraphic[] createSubGraph(IlvGrapher parent) {
    IlvGrapher grapher = new IlvGrapher();
    grapher.setSelectionInvariantSubManagerBounds(true);
    IlvGraphic[] nodes = new IlvGraphic[4];
    IlvPoint[] positions =
        {new IlvPoint(20, 0), new IlvPoint(230, 60), new IlvPoint(20, 60), new IlvPoint(230, 0)};
    int[] linksFrom = {0, 2, 5, 7, 0, 3};
    int[] linksTo = {4, 6, 1, 3, 2, 1};

    IlvGraphic[] inner = createSimpleSubGraph(grapher);

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    for (int i = 0; i < linksFrom.length; i++) {
      int fi = linksFrom[i];
      IlvGraphic from = (fi < nodes.length ? nodes[fi] : inner[fi - nodes.length]);
      int ti = linksTo[i];
      IlvGraphic to = (ti < nodes.length ? nodes[ti] : inner[ti - nodes.length]);
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }

    IlvDefaultManagerFrame frame = (IlvDefaultManagerFrame) grapher.getFrame();
    frame.setOpaque(true);
    frame.setBackground(new Color(100, 255, 100));

    parent.addNode(grapher, 0, true);
    return nodes;
  }

  /**
   * Creates a subgraph.
   */
  private IlvGraphic[] createSimpleSubGraph(IlvGrapher parent) {
    IlvGrapher grapher = new IlvGrapher();
    grapher.setSelectionInvariantSubManagerBounds(true);
    IlvGraphic[] nodes = new IlvGraphic[4];
    IlvPoint[] positions =
        {new IlvPoint(90, 0), new IlvPoint(160, 60), new IlvPoint(90, 60), new IlvPoint(160, 0)};
    int[] linksFrom = {0, 2, 0, 3};
    int[] linksTo = {3, 1, 2, 1};

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    for (int i = 0; i < linksFrom.length; i++) {
      IlvGraphic from = nodes[linksFrom[i]];
      IlvGraphic to = nodes[linksTo[i]];
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }

    IlvDefaultManagerFrame frame = (IlvDefaultManagerFrame) grapher.getFrame();
    frame.setOpaque(true);
    frame.setBackground(new Color(255, 200, 200));

    parent.addNode(grapher, 0, true);
    return nodes;
  }

  /**
   * Sets the attributes of a node.
   */
  private void setNodeAttributes(IlvGraphic node) {
    IlvZoomableLabel zl = (IlvZoomableLabel) node;
    zl.setAntialiasing(true);
    zl.setLeftMargin(6);
    zl.setRightMargin(6);
    zl.setTopMargin(4);
    zl.setBottomMargin(4);
    zl.setBorderOn(true);
    zl.setBackgroundOn(true);
    zl.setBackgroundPaint(Color.yellow);
  }

  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] arg) {
    // Sun recommends putting the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        KeyboardSelApp app = new KeyboardSelApp();
        app.init();

        JFrame frame = new JFrame("Keyboard Selection Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);
        frame.getContentPane().add(app);
        frame.setVisible(true);
      }
    });
  }

}