/*
 * 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.ComponentOrientation;
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.text.MessageFormat;
import java.util.Locale;

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

import ilog.views.IlvDirection;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.event.ManagerSelectionChangedEvent;
import ilog.views.event.ManagerSelectionListener;
import ilog.views.graphlayout.GraphLayoutEvent;
import ilog.views.graphlayout.GraphLayoutEventListener;
import ilog.views.graphlayout.IlvGraphLayout;
import ilog.views.graphlayout.IlvGraphLayoutException;
import ilog.views.graphlayout.IlvGraphLayoutReport;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.recursive.IlvRecursiveLayout;
import ilog.views.graphlayout.tree.IlvTreeLayout;
import ilog.views.graphlayout.uniformlengthedges.IlvUniformLengthEdgesLayout;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * This is a very simple application that uses nested
 * <code>IlvGrapher</code> to perform a recursive layout on a nested graph. It
 * shows how to use recursive layout in applications that are not based on CSS
 * styling.
 */
public class RecursiveLayoutApp extends JRootPane implements ManagerSelectionListener {
  static {
    // Test code whether the demo works in RTL locales
    // Locale newLocale = new Locale("he");
    // IlvSwingUtil.setDefaultLocale(newLocale);
  }

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

  /** The grapher */
  IlvGrapher grapher = new IlvGrapher();

  /** The grapher adapter */
  IlvGrapherAdapter adapter = new IlvGrapherAdapter(grapher);

  /** The view of the grapher */
  IlvManagerView mgrview = new IlvManagerView(grapher);

  /** The recursive layout */
  IlvRecursiveLayout recursiveLayout;

  /** A graph layout event listener */
  LayoutIterationListener layoutListener = new LayoutIterationListener();

  /** GUI Parts */
  JCheckBox sameLayoutEverywhereCheckBox = new JCheckBox(getString("sameLayoutCheckbox.Label"), true);
  JComboBox<String> layoutModeSelector = new JComboBox<String>();

  /** A text field to display messages */
  JTextField msgLine = new JTextField();

  /** the selected grapher during interactions */
  IlvGraphic selectedObject = null;

  /** constants for the layout mode */
  static final int TREE = 0;
  static final int RADIAL = 1;
  static final int TIP_OVER = 2;
  static final int UNIFORM = 3;

  /** The layout mode */
  int layoutMode = -1;

  /** Whether we use reference layout mode or internal provider mode. */
  boolean sameLayoutEverywhere = true;

  /**
   * Initializes the application.
   */
  public void init() {
    showMessage(getString("InitMessage"));

    JPanel panel;

    // set the layout mode and attach the layout
    setLayoutMode(null, TREE);

    // set the zoom range of the manager view
    mgrview.setZoomFactorRange(0.05, 20);

    // 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.setWheelZoomingEnabled(true);
    scrollManView.setWheelScrollingEnabled(true);

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

    // set the layout manager
    getContentPane().setLayout(new BorderLayout(0, 0));

    // fit so far all together
    getContentPane().add("Center", scrollManView);
    getContentPane().add("North", panel = new JPanel());
    panel.setLayout(new GridLayout(2, 2));

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

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

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

    // add this as selection listener
    grapher.addManagerTreeSelectionListener(this);

    // add he checkbox for same parameter everywhere
    sameLayoutEverywhereCheckBox.setToolTipText(getString("sameLayoutCheckbox.Tooltip"));
    sameLayoutEverywhereCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent ev) {
        if (ev.getStateChange() == ItemEvent.SELECTED)
          setSameLayoutEverywhere(true);
        else if (ev.getStateChange() == ItemEvent.DESELECTED)
          setSameLayoutEverywhere(false);
      }
    });

    // create the layout mode selector
    JPanel modePanel = new JPanel();
    modePanel.setLayout(new FlowLayout(FlowLayout.LEADING));
    modePanel.add(new JLabel(getString("layoutModeSelector.Label")));
    modePanel.add(layoutModeSelector);
    layoutModeSelector.setToolTipText(getString("layoutModeSelector1.Tooltip"));
    layoutModeSelector.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        String item = (String) event.getItem();
        if (item.equals(getString("layoutModeSelector.TreeMode")))
          setLayoutMode(selectedObject, TREE);
        else if (item.equals(getString("layoutModeSelector.RadialMode")))
          setLayoutMode(selectedObject, RADIAL);
        else if (item.equals(getString("layoutModeSelector.TipOverMode")))
          setLayoutMode(selectedObject, TIP_OVER);
        else if (item.equals(getString("layoutModeSelector.UniformMode")))
          setLayoutMode(selectedObject, UNIFORM);
      }
    });
    layoutModeSelector.addItem(getString("layoutModeSelector.TreeMode"));
    layoutModeSelector.addItem(getString("layoutModeSelector.RadialMode"));
    layoutModeSelector.addItem(getString("layoutModeSelector.TipOverMode"));
    layoutModeSelector.addItem(getString("layoutModeSelector.UniformMode"));

    // create the graph selector
    JPanel graphPanel = new JPanel();
    graphPanel.setLayout(new FlowLayout(FlowLayout.LEADING));
    graphPanel.add(new JLabel(getString("fileChooser.Label")));
    JComboBox<String> chooser;
    graphPanel.add(chooser = new JComboBox<String>());
    chooser.addItem("sample1");
    chooser.addItem("sample2");
    chooser.addItem("sample3");
    chooser.setToolTipText(getString("fileChooser.Tooltip"));
    chooser.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String fileName = (String) comboBox.getSelectedItem();
        loadGrapher(fileName);
        layout(recursiveLayout, grapher);
      }
    });

    // but rest together
    panel.add(graphPanel);
    panel.add(controlBar);
    panel.add(modePanel);
    panel.add(sameLayoutEverywhereCheckBox);

    // create the panel on bottom
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());
    getContentPane().add("South", bottomPanel);

    // add the message line to the bottom panel
    bottomPanel.add("South", msgLine);
    msgLine.setEditable(false);

    // set the component orientation according to the locale
    Locale loc = IlvSwingUtil.getDefaultLocale();
    ComponentOrientation co = ComponentOrientation.getOrientation(loc);
    getContentPane().applyComponentOrientation(co);

    // load a sample grapher
    loadGrapher("sample1");
    layout(recursiveLayout, grapher);
  }

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

        JFrame frame = new JFrame(getString("Frame.Label"));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(600, 600);
        frame.getContentPane().add(app);
        frame.setVisible(true);
        app.start();
      }
    });
  }

  /**
   * Starts the application.
   */
  public void start() {
    mgrview.fitTransformerToContent();
    mgrview.repaint();
  }

  /**
   * Load a sample IVL file.
   * 
   * @param fileNameBase
   *          The base of the filename, excluding the path prefix and the
   *          extension suffix.
   */
  private void loadGrapher(String fileNameBase) {
    try {
      showMessage(getString("FileReadingStartMessage"), fileNameBase);
      grapher.deleteAll(false);
      try {
        grapher.read(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".ivl"));
      } catch (Exception ex1) {
        // This case occurs only when we pack the entire application into
        // one jar including the data files, and start as application
        grapher.read(getClass().getResource("data/" + fileNameBase + ".ivl"));
      }
      afterRead();
      showMessage(getString("FileReadingDoneMessage"), fileNameBase);
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    }
  }

  /**
   * Postprocessing after reading a graph.
   */
  private void afterRead() {
    // and mark all except subgraphs as nonselectable.
    IlvGraphicEnumeration objects = grapher.getObjects(true);
    while (objects.hasMoreElements()) {
      IlvGraphic g = objects.nextElement();
      if (!(g instanceof IlvGrapher))
        ((IlvManager) g.getGraphicBag()).setSelectable(g, false);
    }

    // in internal provider mode, make sure each subgraph has a layout
    if (!sameLayoutEverywhere)
      createRecLayoutInIntProviderMode();
  }

  /**
   * Sets whether the same layout is used everywhere.
   */
  private void setSameLayoutEverywhere(boolean flag) {
    if (flag == sameLayoutEverywhere)
      return;

    if (flag) {
      // create a new layout in reference layout mode
      createRecLayoutInReferenceMode(layoutMode);
      layoutModeSelector.setToolTipText(getString("layoutModeSelector1.Tooltip"));
    } else {
      createRecLayoutInIntProviderMode();
      layoutModeSelector.setToolTipText(getString("layoutModeSelector2.Tooltip"));
    }

    sameLayoutEverywhere = flag;
    layout(recursiveLayout, grapher);
  }

  /**
   * Sets the layout mode for a specific subgraph.
   */
  private void setLayoutMode(Object subgraph, int mode) {
    if (mode == layoutMode)
      return;

    if (sameLayoutEverywhere)
      // ignore the input grapher, set the layout mode everywhere
      createRecLayoutInReferenceMode(mode);
    else if (subgraph == null || subgraph instanceof IlvGrapher)
      // must have a recursive layout in internal provider mode.
      // Set the layout of that specific grapher.
      recursiveLayout.setLayout(subgraph, createSubLayout(mode));

    layoutMode = mode;
    layout(recursiveLayout, grapher);
  }

  /**
   * Show layout mode at the checkbox.
   */
  private void showLayoutModeAtCheckBox(int mode) {
    // first set the mode, so the setSelectedIndex doesn't need to branch
    // inside the inner of setLayoutMode
    layoutMode = mode;
    layoutModeSelector.setSelectedIndex(mode);
  }

  /**
   * Returns the layout mode of a specific subgraph.
   */
  private int getLayoutMode(Object subgraph) {
    IlvGraphLayout layout = recursiveLayout.getLayout(subgraph);
    if (layout == null)
      return -1;

    if (layout instanceof IlvUniformLengthEdgesLayout)
      return UNIFORM;

    if (layout instanceof IlvTreeLayout) {
      IlvTreeLayout tlayout = (IlvTreeLayout) layout;
      if (tlayout.getLayoutMode() == IlvTreeLayout.RADIAL)
        return RADIAL;
      if (tlayout.getGlobalAlignment() == IlvTreeLayout.TIP_OVER)
        return TIP_OVER;
      return TREE;
    }

    return -1;
  }

  /**
   * Allocate a recursive layout in reference layout mode, so that the same
   * layout style is applied everywhere.
   */
  private void createRecLayoutInReferenceMode(int mode) {
    if (recursiveLayout != null)
      recursiveLayout.detach();

    recursiveLayout = new IlvRecursiveLayout(createSubLayout(mode));
    recursiveLayout.attach(adapter);
  }

  /**
   * Allocate a recursive layout in internal provider mode, so that different
   * layout styles can be applied to different subgraphs.
   */
  private void createRecLayoutInIntProviderMode() {
    // get the old reference layout if any, and detach
    IlvGraphLayout oldreflayout = null;
    if (recursiveLayout != null) {
      oldreflayout = recursiveLayout.getReferenceLayout();
      recursiveLayout.detach();
    }

    // create a new layout in internal provider mode
    recursiveLayout = new IlvRecursiveLayout();
    recursiveLayout.attach(adapter);

    // set the layout style for all subgraphs
    if (oldreflayout != null)
      recursiveLayout.setLayout(null, oldreflayout.copy(), true, true);
    else
      recursiveLayout.setLayout(null, new IlvTreeLayout(), true, true);
  }

  /**
   * Returns a new graph layout instance depending on the input mode. This is
   * used as sublayout of the recursive layout.
   */
  private IlvGraphLayout createSubLayout(int mode) {
    switch (mode) {
    case TREE:
      IlvTreeLayout l0 = new IlvTreeLayout();
      l0.setGlobalLinkStyle(IlvTreeLayout.ORTHOGONAL_STYLE);
      l0.setParentChildOffset(60);
      l0.setFlowDirection(IlvDirection.Bottom);
      return l0;
    case RADIAL:
      IlvTreeLayout l1 = new IlvTreeLayout();
      l1.setLayoutMode(IlvTreeLayout.RADIAL);
      return l1;
    case TIP_OVER:
      IlvTreeLayout l2 = new IlvTreeLayout();
      l2.setGlobalAlignment(IlvTreeLayout.TIP_OVER);
      l2.setFlowDirection(IlvDirection.Bottom);
      return l2;
    case UNIFORM:
      IlvUniformLengthEdgesLayout l3 = new IlvUniformLengthEdgesLayout();
      l3.setPreferredLinksLength(60);
      l3.setRespectNodeSizes(true);
      return l3;
    }

    return new IlvTreeLayout();
  }

  /**
   * Performs the layout of the graph.
   */
  private void layout(IlvRecursiveLayout layout, IlvGrapher grapher) {
    // the layout report instance; this is an object in which
    // the layout algorithm stores information about its behavior
    IlvGraphLayoutReport layoutReport = null;

    showMessage(getString("LayoutStartMessage"));

    // initialize the iteration listener
    layoutListener.initialize();

    try {
      // perform the layout and get the layout report
      layoutReport = layout.performLayout(false, false);

      // print the code from the layout report
      showMessage(getString("LayoutDoneMessage"),
          layoutReport.codeToString(layoutReport.getCode(), IlvLocaleUtil.getCurrentULocale()));
    } catch (IlvGraphLayoutException e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    } finally {
      if (layoutReport != null && layoutReport.getCode() != IlvGraphLayoutReport.NOT_NEEDED) {
        // show all
        mgrview.fitTransformerToContent();
        mgrview.repaint();
      }
    }
  }

  /**
   * Displays a message.
   */
  void showMessage(String message) {
    // the message is displayed in the message line
    msgLine.setText(message);
    msgLine.paintImmediately(0, 0, msgLine.getWidth(), msgLine.getHeight());
  }

  void showMessage(String msgformat, Object val) {
    Object[] args = { val };
    showMessage(MessageFormat.format(msgformat, args));
  }

  /**
   * Returns a string.
   */
  static String getString(String key) {
    return IlvResourceUtil.getString(key, RecursiveLayoutApp.class, IlvLocaleUtil.getCurrentLocale());
  }

  /**
   * This method is called when the selection changes in a manager.
   */
  Override
  public void selectionChanged(ManagerSelectionChangedEvent event) {
    IlvGraphic g = event.getGraphic();
    IlvManager m = event.getManager();

    if (m.isSelected(g)) {
      IlvGraphic previousSelectedObject = selectedObject;
      selectedObject = g;

      if (previousSelectedObject != null && previousSelectedObject != g) {
        IlvManager m1 = (IlvManager) previousSelectedObject.getGraphicBag();
        if (m1.isSelected(previousSelectedObject))
          m1.setSelected(previousSelectedObject, false, true);
      }

      if (g instanceof IlvGrapher)
        showLayoutModeAtCheckBox(getLayoutMode(g));
    } else if (g == selectedObject) {
      selectedObject = null;
      showLayoutModeAtCheckBox(getLayoutMode(null));
    }
  }

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

  /**
   * A graph layout iteration listener. Implementing the interface
   * GraphLayoutEventListener gives you the possibility to receive during the
   * layout information about the behavior of the layout algorithm. This
   * information is contained in the graph layout event.
   * 
   * In our case, we will simply print the percentage completed by the layout
   * each time the method layoutStepPerformed is called, that is after each
   * iteration of the Tree Layout algorithm.
   */
  class LayoutIterationListener implements GraphLayoutEventListener {
    private float oldPercentage;

    /**
     * This method is automatically called by the layout algorithm.
     */
    Override
    public void layoutStepPerformed(GraphLayoutEvent event) {
      float percentage = event.getLayoutReport().getPercentageComplete();
      if (percentage != oldPercentage) {
        showMessage(getString("percentage.Label"), Float.valueOf(percentage));
        oldPercentage = percentage;
      }
    }

    /**
     * Initialize the listener by reseting the toShow variable. This method must
     * be called before the layout is started.
     */
    void initialize() {
      oldPercentage = -1.0f;
    }
  }
}