/*
 * 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.GradientPaint;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;

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.JSplitPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import ilog.views.IlvDirection;
import ilog.views.IlvGraphic;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.graphic.IlvGeneralPath;
import ilog.views.graphic.IlvHalfZoomingGraphic;
import ilog.views.graphic.IlvMarker;
import ilog.views.graphic.IlvRectangle;
import ilog.views.graphic.IlvText;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.graphic.composite.IlvCompositeGraphic;
import ilog.views.graphic.composite.decoration.IlvRoundRectBalloon;
import ilog.views.graphic.composite.layout.IlvAttachmentConstraint;
import ilog.views.graphic.composite.layout.IlvAttachmentLayout;
import ilog.views.graphic.composite.layout.IlvAttachmentLocation;
import ilog.views.graphic.composite.layout.IlvCenteredLayout;
import ilog.views.interactor.IlvManagerMagViewInteractor;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.java2d.IlvBlinkingAction;
import ilog.views.java2d.IlvBlinkingMultiAction;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.java2d.IlvBlinkingColor;
import ilog.views.util.java2d.IlvBlinkingMultiColor;
import ilog.views.util.java2d.IlvBlinkingMultiPaint;
import ilog.views.util.java2d.IlvBlinkingPaint;

/**
 * This is a very simple application that shows how to implement
 * blinking effects in a manager.
 */
SuppressWarnings("serial")
public class BlinkingApp extends JRootPane
{
  // for the performance measurement: the time in milliseconds we spend
  // for redraw
  long sumRedrawTime;

  {
    // 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
    final IlvManager manager = new IlvManager();

    // create the manager views: a main view and an overview
    final IlvManagerView mgrview1 = new ManagerView(manager);
    final IlvManagerView mgrview2 = new ManagerView(manager);

    // create the scroll manager view for the main view
    IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview1);

    // Adjust the overview
    IlvManagerMagViewInteractor overviewInteractor =
      new IlvManagerMagViewInteractor(mgrview1, true);
    overviewInteractor.setDrawingStyle(IlvManagerMagViewInteractor.Wire);
    mgrview2.setInteractor(overviewInteractor);
    mgrview2.setAutoFitToContents(true);
    mgrview2.setBlinkingMode(IlvManagerView.BLINKING_DISABLED);

    // Create a panel with the overview
    JPanel overviewPanel = new JPanel(new BorderLayout(0, 0));
    overviewPanel.add(new JLabel("Overview"), BorderLayout.NORTH);
    overviewPanel.add(mgrview2, BorderLayout.CENTER);

    // Create a panel with the performance history indicator
    JPanel historyPanel = new JPanel(new BorderLayout(0, 0));
    JLabel historyLabel = new JLabel("Redraw Time: 0.0 %");
    historyPanel.add(historyLabel, BorderLayout.NORTH);
    historyPanel.add(new PerformanceHistory(historyLabel), BorderLayout.CENTER);

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

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

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

    mgrview1.setInteractor(controlBar.getSelectInteractor());

    // Now, fill the manager
    fillManager(manager, 1);

    // create the top panel with the toolbar and the checkboxes
    JPanel topPanel = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    topPanel.add(controlBar);

    // add the choice to set the blinking speed
    final JComboBox<String> speedChoice = new JComboBox<String>();
    speedChoice.addItem("1x");
    speedChoice.addItem("2x");
    speedChoice.addItem("3x");
    speedChoice.setSelectedIndex(0);
    topPanel.add(new JLabel("  Blinking Speed: "));
    topPanel.add(speedChoice);
    speedChoice.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        switch (speedChoice.getSelectedIndex()) {
          case 0: fillManager(manager, 1);
                  break;
          case 1: fillManager(manager, 0.5f);
                  break;
          case 2: fillManager(manager, 0.33f);
                  break;
        }
      }
    });

    // add the choice to disable blinking per view
    JCheckBox blinkingOnView1 = new JCheckBox("Main View", true);
    JCheckBox blinkingOnView2 = new JCheckBox("Overview", false);
    topPanel.add(new JLabel("  Blinking on: "));
    topPanel.add(blinkingOnView1);
    topPanel.add(new JLabel(" "));
    topPanel.add(blinkingOnView2);

    blinkingOnView1.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          mgrview1.setBlinkingMode(IlvManagerView.BLINKING_AUTOMATIC);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          mgrview1.setBlinkingMode(IlvManagerView.BLINKING_DISABLED);
        mgrview1.repaint();
      }
    });
    blinkingOnView2.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          mgrview2.setBlinkingMode(IlvManagerView.BLINKING_AUTOMATIC);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          mgrview2.setBlinkingMode(IlvManagerView.BLINKING_DISABLED);
        mgrview2.repaint();
      }
    });

    // put all panels together
    final JSplitPane splitPane1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                                                 overviewPanel,
                                                 historyPanel);
    splitPane1.addHierarchyListener(new HierarchyListener(){
        Override
        public void hierarchyChanged(HierarchyEvent e)
        {
          if (splitPane1.isShowing()){
            splitPane1.setDividerLocation(0.5);
            splitPane1.removeHierarchyListener(this);
          }
        }
      });
    final JSplitPane splitPane2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                                 scrollManView,
                                                 splitPane1);
    splitPane2.addHierarchyListener(new HierarchyListener(){
        Override
        public void hierarchyChanged(HierarchyEvent e)
        {
          if (splitPane2.isShowing()){
            splitPane2.setDividerLocation(0.7);
            splitPane2.removeHierarchyListener(this);
          }
        }
      });
    getContentPane().setLayout(new BorderLayout(0, 0));
    getContentPane().add(splitPane2, BorderLayout.CENTER);
    getContentPane().add(topPanel, BorderLayout.NORTH);
  }

  /**
   * Fills the manager with objects that have various blinking actions,
   * blinking colors or blinking periods set.
   * @param manager The manager to be filled
   * @param blinkingSpeed The base speed of all blinking timings
   */
  private void fillManager(IlvManager manager, float blinkingSpeed)
  {
    if (blinkingSpeed <= 0)
      blinkingSpeed = 1;

    // calculate the timings
    long base300 = (long)(blinkingSpeed * 300);
    long base500 = (long)(blinkingSpeed * 500);
    long base1000 = (long)(blinkingSpeed * 1000);

    // clear the manager
    manager.deleteAll(true);

    // this is a nonpersistent blinking action. It cannot be stored in
    // an IVL file.
    IlvBlinkingAction action1 = new IlvBlinkingMultiAction(3, base1000) {
      Override
      protected void changeState(IlvGraphic obj, int step) {
         // assume the associated object is always an IlvMarker
         IlvMarker marker = (IlvMarker)obj;
         // no applyToObject necessary because the caller does it already
         switch (step) {
           case 0:
             marker.setType(IlvMarker.IlvMarkerCircle);
             break;
           case 1:
             marker.setType(IlvMarker.IlvMarkerPlus);
             break;
           case 2:
             marker.setType(IlvMarker.IlvMarkerCross);
             break;
        }
      }
    };

    // this is a blinking color that shows red, blue and green
    Color color = new IlvBlinkingMultiColor(base300, Color.red,
                                                     Color.blue,
                                                     Color.green);

    IlvMarker marker1 = new IlvMarker(new IlvPoint(10,10),
                                      IlvMarker.IlvMarkerCircle);
    IlvMarker marker2 = new IlvMarker(new IlvPoint(10,30),
                                      IlvMarker.IlvMarkerCircle);
    // blinking actions can be shared among multiple objects
    marker1.setBlinkingAction(action1);
    marker2.setBlinkingAction(action1);
    marker1.setForeground(color);
    marker2.setForeground(color);
    manager.addObject(marker1, true);
    manager.addObject(marker2, true);

    // some labels for the marked
    IlvText text1 = new IlvText(new IlvPoint(20, 15), "Blinking Marker");
    IlvText text2 = new IlvText(new IlvPoint(20, 35), "Blinking Marker");
    manager.addObject(text1, true);
    manager.addObject(text2, true);
    text2.setBlinkingOnPeriod(base500);
    text2.setBlinkingOffPeriod(base500);

    // for this text, use a persistent blinking action
    IlvText text3 = new IlvText(new IlvPoint(10,55), "");
    text3.setBlinkingAction(new LabelBlinkingAction(base1000, base1000,
                                                   "Text with blinking action",
                                                   "Blinking action on text"));
    text3.setEditable(false);
    manager.addObject(text3, true);

    Color color1 = new IlvBlinkingColor(Color.red, Color.yellow,
                                        base300, base1000);
    Color color2 = new IlvBlinkingMultiColor(base300, Color.blue,
                                                  Color.green,
                                                  Color.cyan,
                                                  Color.yellow);
    Color color3 = Color.cyan;

    for (int i = 0; i < 15; i++) {
      IlvRectangle rect =
        new IlvRectangle(new IlvRect(340, 10 + i * 40, 50, 23));
      rect.setFillOn(true);
      switch (i % 3) {
        case 0: rect.setBackground(color1);
                rect.setForeground(color2);
                break;
        case 1: rect.setBackground(color2);
                rect.setForeground(color1);
                break;
        case 2: rect.setBackground(color3);
                rect.setBlinkingOffPeriod(base300);
                rect.setBlinkingOnPeriod(base300);
                break;
      }
      manager.addObject(rect, true);
    }

    Paint paint1 = new GradientPaint(0,0,Color.red,0,100,Color.blue);
    Paint paint2 = new GradientPaint(0,0,Color.blue,0,100,Color.green);
    Paint paint3 = new GradientPaint(0,0,Color.green,0,100,Color.red);
    Paint bpaint1 = new IlvBlinkingMultiPaint(base300, paint1, paint2, paint3);
    Paint bpaint2 = new IlvBlinkingMultiPaint(base300, paint2, paint3, paint1);
    Paint bpaint3 = new IlvBlinkingMultiPaint(base300, paint3, paint1, paint2);

    for (int i = 0; i < 15; i++) {
      IlvGeneralPath gp =
        new IlvGeneralPath(new Rectangle2D.Double(270, 10 + i * 40, 50, 20));
      gp.setFillOn(true);
      gp.setStrokeOn(true);
      switch (i % 3) {
        case 0: gp.setFillPaint(bpaint1);
                gp.setStrokePaint(bpaint2);
                break;
        case 1: gp.setFillPaint(bpaint2);
                gp.setStrokePaint(bpaint3);
                break;
        case 2: gp.setFillPaint(bpaint3);
                gp.setStrokePaint(bpaint1);
                break;
      }

      manager.addObject(gp, true);
    }

    // a blinking action for IlvGeneralPath
    IlvBlinkingAction action2 = new IlvBlinkingMultiAction(3, base1000) {
      Override
      protected void changeState(IlvGraphic obj, int step) {
        // assume the associated object is always an IlvGeneralPath
        IlvGeneralPath gp = (IlvGeneralPath)obj;
        IlvRect r = gp.boundingBox();
        float x = 0;
        float y = 0;
        float w = 50;
        float h = 20;
        // no applyToObject necessary because the caller does it already for us
        switch (step) {
          case 0:
            gp.setShape(new Rectangle2D.Double(x,y,w,h));
            break;
          case 1:
            gp.setShape(new Ellipse2D.Double(x,y,w,h));
            break;
          case 2:
            GeneralPath p = new GeneralPath();
            p.moveTo(x+0.5*w, y);
            p.lineTo(x+w, y+0.5*h);
            p.lineTo(x+0.5*w, y+h);
            p.lineTo(x, y+0.5*h);
            p.closePath();
            gp.setShape(p);
            break;
        } 
        gp.move(r.x, r.y);
      }
    };

    // objects encapsulated in IlvHalfZoomingGraphic
    Paint bpaint4 = new IlvBlinkingPaint(paint1, paint2, base500, base500);
    Paint bpaint5 = new IlvBlinkingPaint(paint2, paint3, base500, base500);
    Paint bpaint6 = new IlvBlinkingPaint(paint3, paint1, base500, base500);

    for (int i = 0; i < 15; i++) {
      IlvGeneralPath gp =
        new IlvGeneralPath(new Rectangle2D.Double(200, 10 + i * 40, 50, 20));
      gp.setFillOn(true);
      gp.setStrokeOn(true);
      gp.setBlinkingAction(action2);
      switch (i % 3) {
        case 0: gp.setFillPaint(bpaint4);
                gp.setStrokePaint(bpaint5);
                break;
        case 1: gp.setFillPaint(bpaint5);
                gp.setStrokePaint(bpaint6);
                break;
        case 2: gp.setFillPaint(bpaint6);
                gp.setStrokePaint(bpaint4);
                break;
      }

      IlvHalfZoomingGraphic hzg =
        new IlvHalfZoomingGraphic(gp, IlvDirection.Center, null, 0.1, 1.5, 1.0);

      manager.addObject(hzg, true);
    }

    // finally a blinking composite graphic
    IlvCompositeGraphic cg = new IlvCompositeGraphic();
    cg.setLayout(new IlvAttachmentLayout());
    IlvRectangle rectangle =
      new IlvRectangle(new IlvRect(50, 200, 40, 40), true, true);
    rectangle.setBackground(color1);
    cg.setChildren(0, rectangle);
    IlvText text = new IlvText();
    text.setLabel("Composite");
    text.setBlinkingAction(new LabelBlinkingAction(base300,base300,
                                                  "Composite",
                                                  "  Graphic"));
    cg.setChildren(1, text);
    cg.setConstraints(1, new IlvAttachmentConstraint(
        IlvAttachmentLocation.TopCenter,
        IlvAttachmentLocation.BottomCenter));

    // Creates a balloon, made of two graphics with a centered layout
    IlvCompositeGraphic balloonGraphic = new IlvCompositeGraphic();
    balloonGraphic.setLayout(new IlvCenteredLayout(new Insets(4,10,4,10)));
  
    IlvRoundRectBalloon balloon = new IlvRoundRectBalloon();
    balloon.setOrientation(SwingConstants.NORTH_EAST);
    balloon.setPointerDepth(10);
    balloon.setShadowThickness(2);
    balloon.setRadius(15);
    balloon.setShadowColor(Color.black);
    balloon.setBorderColor(Color.black);
    balloon.setBalloonColor(new IlvBlinkingColor(Color.green, Color.yellow,
                                                 base300, base1000));
  
    IlvZoomableLabel label = new IlvZoomableLabel(new IlvPoint(0,0),
                   "This is a composite graphic");
    label.setBlinkingOnPeriod(base500);
    label.setBlinkingOffPeriod(base300);
  
    balloonGraphic.setChildren(0,balloon);
    balloonGraphic.setChildren(1,label);
    cg.setChildren(2, balloonGraphic);
    cg.setConstraints(2, new IlvAttachmentConstraint(
        IlvAttachmentLocation.BottomCenter,
        IlvAttachmentLocation.TopRight));

    manager.addObject(cg, true);
  }

  /**
   * 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() {
          BlinkingApp app = new BlinkingApp();
          app.init();

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

  /**
   * A manager view that registers its own redraw time.
   * This allows the performance history to show the percentage of the
   * redraw time compared to the overall time.
   */
  class ManagerView extends IlvManagerView
  {
    /**
     * Constructs a new manager view.
     */
    ManagerView(IlvManager m)
    {
      super(m);

      // some useful default settings of the manager view
      setDoubleBuffering(true);
      setAntialiasing(true);
      setKeepingAspectRatio(true);
      setBackground(Color.white);
      setForeground(SystemColor.windowText);   
      Color xc = SystemColor.windowText;
      xc = new Color(255-xc.getRed(), 255-xc.getGreen(), 255-xc.getBlue());
      setDefaultXORColor(xc);
      setDefaultGhostColor(SystemColor.windowText);
      setZoomFactorRange(0.002, 15.0);
    }

    /**
     * Paints the view.
     * This is overridden to measure the time we need for painting.
     */
    Override
    public void paint(Graphics g)
    {
      long startTime = System.currentTimeMillis();
      super.paint(g);
      long stopTime = System.currentTimeMillis();
      sumRedrawTime += (stopTime - startTime);
    }
  }

  /**
   * The performance history panel.
   * This displays the percentage of the redraw time of the views compared
   * to the overall time.
   */
  class PerformanceHistory extends JPanel implements ActionListener
  {
    long maxPermille = 1000;
    long[] permilles = null;
    long lastTimeStamp = 0;
    JLabel label;

    /**
     * Constructor.
     */
    PerformanceHistory(JLabel label)
    {
      this.label = label;
      this.lastTimeStamp = System.currentTimeMillis();
      new javax.swing.Timer(2000, this).start();
    }

    /**
     * Called by the timer every second. Updates the percentage of the redraw
     * time.
     */
    Override
    public void actionPerformed(ActionEvent evt)
    {
      long timeDiff = System.currentTimeMillis() - lastTimeStamp;
      int permille = (int)(1000.0 * sumRedrawTime / timeDiff);
      addEntry(permille);
      int permilleint = permille / 10;
      int permillefrac = permille % 10;
      if (label != null) {
        label.setText("Redraw Time: " + permilleint + "." + permillefrac + " %");
      }
      repaint();
      sumRedrawTime = 0;
      lastTimeStamp = System.currentTimeMillis();
    }

    /**
     * Paints this performance history panel.
     */
    Override
    public void paint(Graphics g) {
      super.paint(g);
      int w = getWidth();
      int h = getHeight();
      g.setColor(Color.black);
      g.fillRect(0, 0, w, h);
      g.setColor(Color.green);
      for (int i = 0; i < w; i += 15)
        g.drawLine(i, 0, i, h);
      for (int i = 0; i < h; i += 15)
        g.drawLine(0, i, w, i);
      g.setColor(Color.yellow);
      float f = (float)h / (float)maxPermille;
      for (int i = 1; i < permilles.length; i++) {
        g.drawLine(w-1-i, h - (int)(permilles[i-1] * f),
                   w-2-i, h - (int)(permilles[i] * f));
      }
      g.setColor(Color.lightGray);
      g.drawRect(0,0,w,h);
      g.drawRect(1,1,w-2,h-2);
      g.drawRect(0,0,w-2,h-2);
    }

    /**
     * Add an entry to the performance history.
     */
    private void addEntry(long val)
    {
      if (permilles == null || permilles.length == 0) return;
      int n = permilles.length;
      if (n > 1)
        System.arraycopy(permilles, 0, permilles, 1, n-1);
      permilles[0] = val;
      if (val > maxPermille) maxPermille = 3 * val / 2;
    }

    /**
     * Changes the bounds of this panel.
     */
    Override
    public void setBounds(int x, int y, int w, int h)
    {
      long[] old = permilles;
      permilles = new long[w];
      if (old != null) {
        int n = Math.min(old.length, permilles.length);
        System.arraycopy(old, 0, permilles, 0, n);
      }
      super.setBounds(x, y, w, h);
    }

    /**
     * Returns the preferred size.
     */
    Override
    public Dimension getPreferredSize() {
      return new Dimension(60,60);
    }

    /**
     * Returns the minimum size.
     */
    Override
    public Dimension getMinimumSize() {
      return new Dimension(60,60);
    }
  }
}