/*
 * 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.FlowLayout;
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.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;

import ilog.views.IlvApplyObject;
import ilog.views.IlvGraphic;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManager;
import ilog.views.IlvManagerLayer;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvSimplePopupMenu;

/**
 * This is a very simple application that shows how to use
 * Z-ordering with or without the quadtree.
 */
SuppressWarnings("serial")
public class ZOrderingApp extends JRootPane {
  IlvManager manager;
  IlvManagerView mgrview;

  {
    // 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);
  }

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

    // create a manager view
    mgrview = new IlvManagerView(manager);
    mgrview.setSelectedWhenPopupPreferred(true);

    // 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.black;
    IlvHandlesSelection.defaultHandleBackgroundColor = Color.white;
    IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE;

    // create the standard control bar
    IlvJManagerViewControlBar controlBar = new IlvJManagerViewControlBar();
    controlBar.setView(mgrview);

    // modify the interactors such that the demo looks better
    ((IlvSelectInteractor) controlBar.getSelectInteractor()).setOpaqueMove(true);
    ((IlvZoomViewInteractor) controlBar.getZoomViewInteractor()).setPermanent(true);

    // set the initial interactor
    mgrview.setInteractor(controlBar.getSelectInteractor());

    // -----------------------------------------------------------------------
    // Now, fill the manager and set up the pop-up menus

    // register view view to the pop-up manager
    IlvPopupMenuManager.registerView(mgrview);

    // register the menus under different names
    IlvPopupMenuManager.registerMenu("MENU1", createMenu());

    // fill the manager with graphic objects that use menu1
    Random rand = new Random(0);
    for (int i = 0; i < 5; i++) {
      for (int j = 0; j < 28; j++) {
        // double x = rand.nextDouble() * 400;
        // double y = rand.nextDouble() * 400;
        double x = 5 + i * 100 + (j % 2) * 45;
        double y = 5 + j * 15;
        IlvZoomableLabel graphic = new IlvZoomableLabel(new IlvPoint(x, y), "Z-Order: " + i, false);
        graphic.setAntialiasing(true);
        graphic.setLeftMargin(6);
        graphic.setRightMargin(6);
        graphic.setTopMargin(4);
        graphic.setBottomMargin(4);
        graphic.setBorderOn(true);
        graphic.setBackgroundOn(true);
        int r = (int) (80 + rand.nextFloat() * (255 - 80));
        int g = (int) (80 + rand.nextFloat() * (255 - 80));
        int b = (int) (80 + rand.nextFloat() * (255 - 80));
        r = Math.max(0, Math.min(r, 255));
        g = Math.max(0, Math.min(g, 255));
        b = Math.max(0, Math.min(b, 255));
        graphic.setBackgroundPaint(new Color(r, g, b));

        // set the pop-up menu or the graphic
        graphic.setPopupMenuName("MENU1");

        // add the graphic to the manager
        manager.addObject(graphic, 0, true);
      }
    }

    // enable Z-Ordering
    final IlvManagerLayer layer = manager.getManagerLayer(0);
    layer.setZOrdering(true);

    // create a checkbox that allows to disable the Z-ordering in the layer
    JCheckBox useZOrder = new JCheckBox("Z-Order", true);
    useZOrder.setToolTipText("Enable or disable the Z-ordering");
    useZOrder.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          layer.setZOrdering(true);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          layer.setZOrdering(false);
        manager.reDraw();
      }
    });

    // create a checkbox that allows to disable the quadtree in the layer
    JCheckBox useQuadTree = new JCheckBox("Quadtree", true);
    useQuadTree.setToolTipText("Enable or disable the quad tree");
    useQuadTree.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          layer.setQuadtreeEnabled(true);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          layer.setQuadtreeEnabled(false);
        manager.reDraw();
      }
    });

    /**
     * To enable benchmarking
     *
    JButton bench = new JButton("Bench");
    bench.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("Quadtree " + layer.isQuadtreeEnabled());
        System.out.println("Z-Ordering " + layer.isZOrdering());
        long s = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++)
          manager.getObject(new IlvPoint(i,i), mgrview);
        long t = System.currentTimeMillis();
        System.out.println("Time for 1000 hittest: " + (t-s) + " ms");
        s = System.currentTimeMillis();
        IlvApplyObject f = new IlvApplyObject() {
          public void apply(IlvGraphic g, Object arg) {}
        };
        for (int i = 0; i < 1000; i++)
          manager.mapIntersects(f, null, new IlvRect(i,i,100,100), new IlvTransformer());
        t = System.currentTimeMillis();
        System.out.println("Time for 1000 empty mapIntersects: " + (t-s) + " ms");
        int n = layer.getCardinal();
        s = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++)
          layer.setIndex(layer.getObject(n-1), i);
        t = System.currentTimeMillis();
        System.out.println("Time for 1000 setIndex: " + (t-s) + " ms");
        s = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++)
          layer.getIndex(layer.getObject(n-1));
        t = System.currentTimeMillis();
        System.out.println("Time for 1000 getIndex: " + (t-s) + " ms");
      }
    });
     */

    // create the top panel with the toolbar and the pop-up checkbox
    JPanel topPanel = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    topPanel.add(controlBar);
    topPanel.add(useZOrder);
    topPanel.add(useQuadTree);
    // topPanel.add(bench);

    // 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);

    updateLabelTexts();
  }

  /**
   * Update the text in the label to display the current Z-order.
   */
  private void updateLabelTexts() {
    manager.applyToObjects(manager.getObjects(), new IlvApplyObject() {
      Override
      public void apply(IlvGraphic g, Object arg) {
        if (g instanceof IlvZoomableLabel) {
          IlvManagerLayer layer = manager.getManagerLayer(g);
          int zOrder = layer.getIndex(g);
          String s;
          if (zOrder < 10)
            s = "00" + zOrder;
          else if (zOrder < 100)
            s = "0" + zOrder;
          else
            s = "" + zOrder;
          ((IlvZoomableLabel) g).setLabel("Z-Order: " + s);
        }
      }
    }, null, true);
  }

  /**
   * Creates a very simple pop-up menu.
   * In principle, you can use any JPopupMenu at the graphic object.
   * However, the IlvSimplePopupMenu is very conveniant to configure, hence
   * we use it in this sample.
   */
  private JPopupMenu createMenu() {
    // create the action listener for the pop-up menu
    ActionListener actionListener = new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {

        // retrieve the selected menu item
        JMenuItem m = (JMenuItem) e.getSource();

        // retrieve the graphic that has this pop-up menu
        IlvPopupMenuContext context = IlvPopupMenuManager.getPopupMenuContext(m);
        IlvGraphic graphic = context.getGraphic();

        // do the action
        if (IlvSimplePopupMenu.getMenuItemText(m).equals("To front"))
          moveToFront(graphic);
        else if (IlvSimplePopupMenu.getMenuItemText(m).equals("Forward"))
          moveForward(graphic);
        else if (IlvSimplePopupMenu.getMenuItemText(m).equals("Backward"))
          moveBackward(graphic);
        else if (IlvSimplePopupMenu.getMenuItemText(m).equals("To back"))
          moveToBack(graphic);

        // select the object (just to have a visual feedback which object has
        // changed)
        manager.deSelectAll(true);
        manager.setSelected(graphic, true, true);
      }
    };

    // create a simple pop-up menu
    IlvSimplePopupMenu menu = new IlvSimplePopupMenu("Menu1",
        "To front | Forward | Backward | To back", null, actionListener);

    return menu;
  }

  /**
   * Move a graphic to the top of the Z-order.
   */
  private void moveToFront(IlvGraphic g) {
    IlvManager manager = (IlvManager) g.getGraphicBag();
    IlvManagerLayer layer = manager.getManagerLayer(g);
    layer.setIndex(g, layer.getCardinal() - 1);
    // this also redraws
    updateLabelTexts();
  }

  /**
   * Move a graphic to the bottom of the Z-order.
   */
  private void moveToBack(IlvGraphic g) {
    IlvManager manager = (IlvManager) g.getGraphicBag();
    IlvManagerLayer layer = manager.getManagerLayer(g);
    layer.setIndex(g, 0);
    // this also redraws
    updateLabelTexts();
  }

  /**
   * Move a graphic one step forward in the Z-order
   * Well, not quite: if we have 1000 objects, then we would need up to
   * 1000 times move forward operations to move the object visible forward.
   * But visibly, only something happens at the Z-order of the overlapping
   * objects, so we shorten this by moving forward with respect to the
   * current overlapping. That is, we move it to the Z-order so that it
   * appears on top of the lowest overlapping object that is currently on top
   * of the input object
   */
  private void moveForward(IlvGraphic g) {
    IlvManager manager = (IlvManager) g.getGraphicBag();
    IlvManagerLayer layer = manager.getManagerLayer(g);
    IlvTransformer t = mgrview.getTransformer();
    IlvRect trect = g.boundingBox(t);
    IlvRect rect = new IlvRect(trect);
    t.boundingBox(rect, true);
    IntersectionCollector f = new IntersectionCollector();
    layer.mapIntersects(rect, trect, f, null, t);
    int zOrder = layer.getIndex(g);
    int bestZOrder;
    if (f.result.size() <= 1)
      // just move one up. Nothing overlaps, so there will not be any
      // immediate visual effect
      bestZOrder = zOrder + 1;
    else {
      // search in the result the closest object with higher Z-Order.
      // Move to that Z-Order, because this has an immediate visual effect.
      bestZOrder = layer.getCardinal();
      for (int i = 0; i < f.result.size(); i++) {
        IlvGraphic obj = f.result.get(i);
        int otherZOrder = layer.getIndex(obj);
        if (zOrder < otherZOrder && otherZOrder < bestZOrder)
          bestZOrder = otherZOrder;
      }
    }
    layer.setIndex(g, bestZOrder);
    // this also redraws
    updateLabelTexts();
  }

  /**
   * Move a graphic one step backward in the Z-order
   * Well, not quite: if we have 1000 objects, then we would need up to
   * 1000 times move backward operations to move the object visible backward.
   * But visibly, only something happens at the Z-order of the overlapping
   * objects, so we shorten this by moving backward with respect to the
   * current overlapping. That is, we move it to the Z-order so that it
   * appears below of the highest overlapping object that is currently below
   * of the input object
   */
  private void moveBackward(IlvGraphic g) {
    IlvManager manager = (IlvManager) g.getGraphicBag();
    IlvManagerLayer layer = manager.getManagerLayer(g);
    IlvTransformer t = mgrview.getTransformer();
    IlvRect trect = g.boundingBox(t);
    IlvRect rect = new IlvRect(trect);
    t.boundingBox(rect, true);
    IntersectionCollector f = new IntersectionCollector();
    layer.mapIntersects(rect, trect, f, null, t);
    int zOrder = layer.getIndex(g);
    int bestZOrder;
    if (f.result.size() <= 1)
      // just move one down. Nothing overlaps, so there will not be any
      // immediate visual effect
      bestZOrder = zOrder - 1;
    else {
      // search in the result the closest object with lower Z-Order.
      // Move to that Z-Order, because this has an immediate visual effect.
      bestZOrder = -1;
      for (int i = 0; i < f.result.size(); i++) {
        IlvGraphic obj = f.result.get(i);
        int otherZOrder = layer.getIndex(obj);
        if (bestZOrder < otherZOrder && otherZOrder < zOrder)
          bestZOrder = otherZOrder;
      }
    }
    layer.setIndex(g, bestZOrder);
    // this also redraws
    updateLabelTexts();
  }

  /**
   * A collector apply opject.
   */
  class IntersectionCollector implements IlvApplyObject {
    List<IlvGraphic> result = new ArrayList<IlvGraphic>();

    Override
    public void apply(IlvGraphic g, Object arg) {
      result.add(g);
    }
  }

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

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