/*
 * 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.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
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.Iterator;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JTextField;

import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicBag;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManager;
import ilog.views.IlvObjectWithSelection;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerViewBar;
import ilog.views.graphic.linkbundle.IlvLinkBundle;
import ilog.views.graphlayout.IlvGraphModel;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.IlvInappropriateLinkException;
import ilog.views.sdm.util.IlvSDMMutableStyleSheet;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvSimplePopupMenu;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * This is a very simple application that uses the
 * <code>IlvDiagrammer</code> to perform a link layout on bundles of links. It
 * loads a style sheet containing the link bundle specification. It allows you
 * to set various link layout parameters by using the
 * <code>IlvSDMMutableStyleSheet</code>.
 */
public class LinkBundleApp 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);
  }

  /** The diagrammer */
  IlvDiagrammer diagrammer = new IlvDiagrammer();

  /** The style sheet for temporary changes */
  IlvSDMMutableStyleSheet styleSheet = new IlvSDMMutableStyleSheet(diagrammer.getEngine(), true, false);

  /** Whether bundles are currently expanded */
  boolean expandedState = true;

  /**
   * Initializes the application.
   */
  public void init() {
    // we use the standard diagrammer toolbar
    IlvDiagrammerViewBar toolbar = new IlvDiagrammerViewBar();

    // various settings of the diagrammer
    diagrammer.setSelectMode(true);
    diagrammer.setScrollable(true);
    diagrammer.setEditingAllowed(true);
    diagrammer.setMinimumZoom(0.02);
    diagrammer.setMaximumZoom(10);
    diagrammer.getView().setBackground(Color.white);
    diagrammer.getView().setForeground(SystemColor.windowText);

    // for the pop-up menu
    // register view view to the pop-up manager
    IlvPopupMenuManager.registerView(diagrammer.getView());

    // register the menus under different names
    IlvPopupMenuManager.registerMenu("COLLAPSE_BUNDLE", createCollapseMenu());
    IlvPopupMenuManager.registerMenu("EXPAND_BUNDLE", createExpandMenu());

    // create the file selector
    JComboBox<String> cssSelector = new JComboBox<String>();
    cssSelector.addItem("Composite links");
    cssSelector.addItem("Simple links");
    cssSelector.addItem("Nested bundles");
    cssSelector.setToolTipText("Select a sample graph");
    cssSelector.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String cssName = (String) comboBox.getSelectedItem();
        if ("Composite links".equals(cssName))
          cssName = "composite";
        else if ("Simple links".equals(cssName))
          cssName = "flat";
        else if ("Nested bundles".equals(cssName))
          cssName = "nested";
        loadCSS(cssName);
      }
    });

    // create the layout button
    JButton layoutButton = new JButton("Layout");
    layoutButton.setToolTipText("Perform a link layout");
    layoutButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
      }
    });

    // create the top panel with the toolbar and the file selector
    JPanel topPanel = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    topPanel.add(toolbar);
    topPanel.add(new JLabel("Select CSS:"));
    topPanel.add(cssSelector);

    // create the bottom panel with the further options
    JPanel bottomPanel = new JPanel();
    JPanel bottomPanel1 = new JPanel();
    JPanel bottomPanel2 = new JPanel();
    bottomPanel.setLayout(new GridLayout(2, 0));
    bottomPanel1.setLayout(new FlowLayout(FlowLayout.LEFT));
    bottomPanel2.setLayout(new FlowLayout(FlowLayout.LEFT));
    bottomPanel.add(bottomPanel1);
    bottomPanel.add(bottomPanel2);
    addFurtherOptions(bottomPanel1, bottomPanel2);

    // put diagrammer, top panel and bottom panel together
    getContentPane().setLayout(new BorderLayout(0, 0));
    getContentPane().add(diagrammer, BorderLayout.CENTER);
    getContentPane().add(topPanel, BorderLayout.NORTH);
    getContentPane().add(bottomPanel, BorderLayout.SOUTH);

    // prepare the mutual style sheet
    styleSheet.setDeclaration("link", "singleConnectionPoint", "false");
    styleSheet.setDeclaration("Subobject#LinkBundleA", "singleConnectionPoint", "false");
    styleSheet.setDeclaration("Subobject#LinkBundleB", "singleConnectionPoint", "false");

    styleSheet.setDeclaration("link", "offset", "5");
    styleSheet.setDeclaration("Subobject#LinkBundleA", "offset", "5");
    styleSheet.setDeclaration("Subobject#LinkBundleB", "offset", "5");

    styleSheet.setDeclaration("link", "frame", "+BundleFrame");
    styleSheet.setDeclaration("Subobject#LinkBundleA", "frame", "+BundleFrameA");
    styleSheet.setDeclaration("Subobject#LinkBundleB", "frame", "+BundleFrameB");

    styleSheet.setDeclaration("Subobject#BundleFrame", "margin", "3");
    styleSheet.setDeclaration("Subobject#BundleFrameA", "margin", "3");
    styleSheet.setDeclaration("Subobject#BundleFrameB", "margin", "3");

    styleSheet.setDeclaration("link", "collapsed", "false");

    // load the CSS file
    loadCSS("composite");

    // load the initial sample grapher
    loadDataFile("sample1");
  }

  /**
   * Fill the bottom panels with further options.
   */
  private void addFurtherOptions(JPanel bottomPanel1, JPanel bottomPanel2) {
    // ----------------------------------------
    // Link style

    bottomPanel1.add(new JLabel("Link Style:"));
    JComboBox<String> linkStyleChooser = new JComboBox<String>();
    bottomPanel1.add(linkStyleChooser);
    linkStyleChooser.setToolTipText("Select the link shape");

    linkStyleChooser.addItem("Orthogonal");
    linkStyleChooser.addItem("Direct");
    linkStyleChooser.addItem("Straight");

    linkStyleChooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        String styleName = (String) event.getItem();

        if (styleName.equals("Orthogonal"))
          setLinkStyle("ORTHOGONAL_STYLE");
        else if (styleName.equals("Direct"))
          setLinkStyle("DIRECT_STYLE");
        else if (styleName.equals("Straight"))
          setLinkStyle("STRAIGHT_STYLE");
        else
          throw new RuntimeException("Unsupported link style: " + styleName);
      }
    });

    // ----------------------------------------
    // Single connection point

    JCheckBox singleConnectionPointCheckbox = new JCheckBox("Single connection point", false);
    bottomPanel1.add(singleConnectionPointCheckbox);
    singleConnectionPointCheckbox.setToolTipText("Enables the single connection point mode");
    singleConnectionPointCheckbox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          styleSheet.setDeclaration("link", "singleConnectionPoint", "true");
          styleSheet.setDeclaration("Subobject#LinkBundleA", "singleConnectionPoint", "true");
          styleSheet.setDeclaration("Subobject#LinkBundleB", "singleConnectionPoint", "true");
        } else if (e.getStateChange() == ItemEvent.DESELECTED) {
          styleSheet.setDeclaration("link", "singleConnectionPoint", "false");
          styleSheet.setDeclaration("Subobject#LinkBundleA", "singleConnectionPoint", "false");
          styleSheet.setDeclaration("Subobject#LinkBundleB", "singleConnectionPoint", "false");
        }
      }
    });

    // --------------------------------------------
    // Offset

    JLabel offsetLabel = new JLabel("Offset:");
    String tooltip = "Sets the offset between the sublinks of the bundles.";
    offsetLabel.setToolTipText(tooltip);
    bottomPanel1.add(offsetLabel);
    final JTextField offsetField = new JTextField("5.0    ");
    offsetField.setToolTipText(tooltip);
    bottomPanel1.add(offsetField);
    offsetField.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        float value = Float.valueOf(offsetField.getText().trim()).floatValue();
        offsetField.setText("" + value);
        styleSheet.setDeclaration("link", "offset", "" + value);
        styleSheet.setDeclaration("Subobject#LinkBundleA", "offset", "" + value);
        styleSheet.setDeclaration("Subobject#LinkBundleB", "offset", "" + value);
      }
    });

    // -----------------------------------------------
    // Link Bundle Frame

    final JPanel bundleFramePanel = new JComfortPanel();

    final JLabel marginLabel = new JLabel("Frame margin:");
    tooltip = "Sets the margin";
    marginLabel.setToolTipText(tooltip);
    final JTextField marginField = new JTextField("3.0    ");
    marginField.setToolTipText(tooltip);
    marginField.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        float value = Float.valueOf(marginField.getText().trim()).floatValue();
        marginField.setText("" + value);
        styleSheet.setDeclaration("Subobject#BundleFrame", "margin", "" + value);
        styleSheet.setDeclaration("Subobject#BundleFrameA", "margin", "" + value);
        styleSheet.setDeclaration("Subobject#BundleFrameB", "margin", "" + value);
      }
    });

    final JCheckBox bundleFrameCheckbox = new JCheckBox("Bundle frame", true);
    bundleFrameCheckbox.setToolTipText("Enables the use of a frame around the link bundles");
    bundleFrameCheckbox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        // specify the new link style option
        if (e.getStateChange() == ItemEvent.SELECTED) {
          styleSheet.setDeclaration("link", "frame", "+BundleFrame");
          styleSheet.setDeclaration("Subobject#LinkBundleA", "frame", "+BundleFrameA");
          styleSheet.setDeclaration("Subobject#LinkBundleB", "frame", "+BundleFrameB");
          marginLabel.setEnabled(true);
          marginField.setEnabled(true);
        } else if (e.getStateChange() == ItemEvent.DESELECTED) {
          styleSheet.setDeclaration("link", "frame", "");
          styleSheet.setDeclaration("Subobject#LinkBundleA", "frame", "");
          styleSheet.setDeclaration("Subobject#LinkBundleB", "frame", "");
          marginLabel.setEnabled(false);
          marginField.setEnabled(false);
        }
      }
    });

    bundleFramePanel.add(bundleFrameCheckbox);
    bundleFramePanel.add(marginLabel);
    bundleFramePanel.add(marginField);
    bottomPanel2.add(bundleFramePanel);

    // ----------------------------------------
    // Expand/collapse button

    final JButton expandCollapseButton = new JButton("Collapse");
    bottomPanel2.add(expandCollapseButton);
    refreshExpandCollapseButton(expandCollapseButton, expandedState);
    expandCollapseButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        expandedState = !expandedState;
        refreshExpandCollapseButton(expandCollapseButton, expandedState);
        bundleFramePanel.setEnabled(expandedState);
        if (expandedState && !bundleFrameCheckbox.isSelected()) {
          marginLabel.setEnabled(false);
          marginField.setEnabled(false);
        }
        styleSheet.setAdjusting(true);
        try {
          if (expandedState)
            styleSheet.setDeclaration("link", "collapsed", "false");
          else
            styleSheet.setDeclaration("link", "collapsed", "true");
          // remove all declatation about individual expand or collapse.
          // This is only needed because the pop-up menus introduce such
          // declarations
          List<String> individualLinkSelectors = new ArrayList<String>();
          Iterator<?> iter = styleSheet.getRuleSelectors();
          while (iter.hasNext()) {
            String selector = (String) iter.next();
            // need to collect selectors first to avoid concurrent modifications
            if (selector.startsWith("#link"))
              individualLinkSelectors.add(selector);
          }
          Iterator<String> iter2 = individualLinkSelectors.iterator();
          while (iter2.hasNext()) {
            String selector = iter2.next();
            styleSheet.removeDeclaration(selector, "collapsed");
          }
        } finally {
          styleSheet.setAdjusting(false);
        }
      }
    });
  }

  /**
   * Refreshes the button for expanding/collapsing link bundles.
   */
  private void refreshExpandCollapseButton(JButton button, boolean expand) {
    button.setText(expand ? "Collapse" : "Expand");
    button.setToolTipText(expand ? "Collapse the link bundles to show the overview link"
        : "Expand the link bundles to show the sublinks");
  }

  /**
   * Load a sample data file.
   * 
   * @param fileNameBase
   *          The base of the filename, excluding the path prefix and the
   *          extension suffix.
   */
  private void loadDataFile(String fileNameBase) {
    try {
      diagrammer.setDataFile(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".xml"));
      diagrammer.fitToContents();
    } catch (Exception e) {
    }
  }

  /**
   * Load a CSS file.
   * 
   * @param fileNameBase
   *          The base of the filename, excluding the path prefix and the
   *          extension suffix.
   */
  private void loadCSS(String fileNameBase) {
    try {
      // load the main style file
      diagrammer.setStyleSheet(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".css"));
      // load a mutable style file that we use for temporary style changes
      diagrammer.getEngine().setStyleSheets(1, styleSheet.toString());
    } catch (Exception e) {
    }
  }

  /**
   * Sets the orithogonal link style or direct link style.
   */
  private void setLinkStyle(String style) {
    if (style.equals("STRAIGHT_STYLE")) {
      styleSheet.setDeclaration("LinkLayout", "enabled", "false");
      deleteIntermediateLinkPoints(false);
    } else {
      styleSheet.setDeclaration("LinkLayout", "enabled", "true");
      styleSheet.setDeclaration("LinkLayout", "globalLinkStyle", style);
    }
  }

  private void deleteIntermediateLinkPoints(boolean redraw) {
    // remove all intermediate points from all links
    IlvGrapher grapher = diagrammer.getEngine().getGrapher();
    IlvGraphModel graphModel = new IlvGrapherAdapter(grapher);
    IlvGraphicEnumeration e = grapher.getObjects();
    IlvGraphic g;
    while (e.hasMoreElements()) {
      g = e.nextElement();
      if (grapher.isLink(g)) {
        IlvLinkImage link = (IlvLinkImage) g;
        try {
          graphModel.reshapeLink(link, null, null, 0, 0, null, redraw);
        } catch (IlvInappropriateLinkException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

  private JPopupMenu createCollapseMenu() {
    return createMenu(true);
  }

  private JPopupMenu createExpandMenu() {
    return createMenu(false);
  }

  private JPopupMenu createMenu(boolean collapse) {
    // 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 (m.getText().equals("Collapse")) {
          if (graphic instanceof IlvLinkBundle) {
            IlvLinkBundle bundle = (IlvLinkBundle) graphic;
            collapse(bundle, true);
          }
        }
        if (m.getText().equals("Expand")) {
          // the expand menu is on the overview link
          if (graphic.getGraphicBag() instanceof IlvLinkBundle) {
            IlvLinkBundle ownerBundle = (IlvLinkBundle) graphic.getGraphicBag();
            if (ownerBundle.getOverviewLink() == graphic) {
              graphic = ownerBundle;
            }
          }
          if (graphic instanceof IlvLinkBundle) {
            IlvLinkBundle bundle = (IlvLinkBundle) graphic;
            collapse(bundle, false);
          }
        }
      }
    };

    IlvSimplePopupMenu menu = new IlvSimplePopupMenu("Menu1", collapse ? "Collapse" : "Expand", null, actionListener) {

      // configure checkbox of the pop-up before it gets displayed
      Override
      protected void beforeDisplay(IlvPopupMenuContext context) {
        // always call super.beforeDisplay first
        super.beforeDisplay(context);
        // retrieve the graphic
        IlvGraphic graphic = context.getGraphic();
        // deselect all objects in the manager
        IlvGraphicBag bag = graphic.getGraphicBag();
        while (bag != null && !(bag instanceof IlvManager)) {
          bag = ((IlvGraphic) bag).getGraphicBag();
        }
        if (bag instanceof IlvManager)
          ((IlvManager) bag).deSelectAll(true, true);
        // select this graphic;
        bag = graphic.getGraphicBag();
        if (bag instanceof IlvLinkBundle) {
          IlvLinkBundle ownerBundle = (IlvLinkBundle) graphic.getGraphicBag();
          if (ownerBundle.getOverviewLink() == graphic) {
            graphic = ownerBundle;
            bag = graphic.getGraphicBag();
          }
        }
        if (bag instanceof IlvObjectWithSelection) {
          ((IlvObjectWithSelection) bag).setSelected(graphic, true, true);
        }
      }
    };

    return menu;
  }

  /**
   * Collapse or expand an individual link by adding a rule to the mutable style
   * sheet about the specific link. This works only for the top level bundles,
   * not for nested bundles.
   */
  private void collapse(IlvLinkBundle link, boolean collapse) {
    // only top level bundles can have a collapse/expand menu in SDM
    Object linkModelObj = diagrammer.getEngine().getObject(link);
    if (linkModelObj == null)
      return;
    String id = diagrammer.getID(linkModelObj);
    styleSheet.setDeclaration("#" + id, "collapsed", collapse ? "true" : "false");
  }

  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        LinkBundleApp app = new LinkBundleApp();
        app.init();

        JFrame frame = new JFrame("Link Bundle Sample");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(550, 650);
        frame.getContentPane().add(app);
        frame.setVisible(true);

      }
    });
  }

  private static class JComfortPanel extends JPanel {
    Override
    public void setEnabled(boolean enabled) {
      Component[] components = getComponents();
      for (int i = 0; i < components.length; i++)
        components[i].setEnabled(enabled);
    }
  }

}