/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © 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 ilog.views.IlvManager;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvDirection;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManagerView;

import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;

import ilog.views.event.ManagerSelectionListener;
import ilog.views.event.ManagerSelectionChangedEvent;

import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.swing.IlvJManagerViewControlBar;

import ilog.views.graphlayout.IlvGraphLayout;
import ilog.views.graphlayout.IlvGraphLayoutReport;
import ilog.views.graphlayout.IlvGraphLayoutException;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.GraphLayoutEvent;
import ilog.views.graphlayout.GraphLayoutEventListener;
import ilog.views.graphlayout.recursive.IlvRecursiveLayout;
import ilog.views.graphlayout.tree.IlvTreeLayout;
import ilog.views.graphlayout.uniformlengthedges.IlvUniformLengthEdgesLayout;

import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.IlvLocaleUtil;

import ilog.views.util.swing.IlvSwingUtil;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;
import javax.swing.*;

/**
 * This is a very simple applet/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 RecursiveLayoutApplet 
  extends JApplet
  implements ManagerSelectionListener
{
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids that the applet
    // tries to load resource bundles for other locales over the net,
    // even if the current locale of the browser is different.
    if (IlvResourceUtil.isInApplet())
      IlvResourceUtil.setAvailableResourceSuffixes("", "_ja");

    // 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 layoutModeSelector = new JComboBox();

  /** 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 applet/application.
   */
  public void init()
  {
    showMessage(getString("InitMessage"));

    JPanel panel;
    super.init();

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

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

    // 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() {
      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() {
      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 chooser;
    graphPanel.add(chooser = new JComboBox());
    chooser.addItem("sample1");
    chooser.addItem("sample2");    
    chooser.addItem("sample3");
    chooser.setToolTipText(getString("fileChooser.Tooltip"));
    chooser.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
          JComboBox comboBox = (JComboBox)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);
  }

  /**
   * Called when this applet is being reclaimed in order to destroy
   * any resources that it has allocated.
   */
  public void destroy()
  {
    super.destroy();

    // This method is intended to workaround memory management issues
    // in the Sun JRE. Please refer to the method documentation for more
    // details and a description of the known issues.
    IlvSwingUtil.cleanupApplet();
  }

  /**
   * 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() {
        public void run() {
          RecursiveLayoutApplet applet = new RecursiveLayoutApplet();
          applet.init();

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

  /**
   * Starts the applet.
   */
  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, RecursiveLayoutApplet.class,
                                     IlvLocaleUtil.getCurrentLocale());
  }

  /**
   * This method is called when the selection changes in a manager.
   */
  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.
     */
    public void layoutStepPerformed(GraphLayoutEvent event)
    {
      float percentage = event.getLayoutReport().getPercentageComplete();
      if (percentage != oldPercentage) {
        showMessage(getString("percentage.Label"), new Float(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;
    }
  }
}