/*
 * 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.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;

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

import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.diagrammer.application.IlvDiagrammerToolBar;
import ilog.views.diagrammer.application.IlvDiagrammerViewBar;
import ilog.views.graphlayout.IlvGraphLayout;
import ilog.views.graphlayout.random.IlvRandomLayout;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.renderer.graphlayout.IlvGraphLayoutRenderer;
import ilog.views.util.IlvProductUtil;

/**
 * This is a very simple application that shows how to specify
 * constraints of hierarchical layout in a Diagrammer application. It shows
 * three variants: - how to specify constraints in the style sheet directly. See
 * sampleCSS.css in the resources directory of this sample. - how to specify
 * constraints using a constraints URL. See sampleURL.css and Constraints.txt in
 * the resources directory of this sample. - how to code constraints
 * programmatically (even though this rarely makes sense when using CSS. It
 * makes more sense if CSS is not used at all).
 */
public class HierarchicalConstraintsApp extends JRootPane {
  {
    // 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);
  }

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

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

  /**
   * Initializes the application.
   */
  public void init() {
    IlvDiagrammerToolBar toolbar = new IlvDiagrammerViewBar(JToolBar.HORIZONTAL);

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

    JComboBox<String> styleSheetSelector = new JComboBox<String>();
    styleSheetSelector.addItem("CSS");
    styleSheetSelector.addItem("URL");
    styleSheetSelector.addItem("No Constraints");
    styleSheetSelector.setToolTipText("The way constraints are specified");
    styleSheetSelector.setSelectedIndex(2);
    styleSheetSelector.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String selection = (String) comboBox.getSelectedItem();
        if (selection.equals("No Constraints"))
          selection = "NoConstraints";
        loadStyleSheet(selection);
      }
    });

    // create the randomize button
    JButton randomizeButton = new JButton("Random");
    randomizeButton.setToolTipText("Randomize the node positions");
    randomizeButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        randomize();
      }
    });

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

    // create the panel for the layout buttons
    JPanel layoutButtonPanel = new JPanel();
    layoutButtonPanel.setLayout(new FlowLayout());
    layoutButtonPanel.add(randomizeButton);
    layoutButtonPanel.add(layoutButton);

    // 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("Constraints from:"));
    topPanel.add(styleSheetSelector);

    // create the bottom panel with the layout buttons and the message line
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());
    bottomPanel.add(layoutButtonPanel, BorderLayout.NORTH);
    bottomPanel.add(msgLine, BorderLayout.SOUTH);
    msgLine.setEditable(false);

    // put diagram component, 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);

    // tell the actions of the toolbar what the diagram component is.
    IlvDiagrammerAction.updateActions(getRootPane(), diagrammer);

    // load the sample data
    loadDataFile("Sample");

    // load the initial style file
    loadStyleSheet("NoConstraints");
  }

  /**
   * Called when the app is made visible.
   */
  public void start() {
    // show everything
    diagrammer.fitToContents();
  }

  /**
   * Loads a sample data file.
   * 
   * @param fileNameBase
   *          The base of the filename, excluding the path prefix and the
   *          extension suffix.
   */
  private void loadDataFile(String fileNameBase) {
    showMessage("Reading " + fileNameBase + ".xml ...");
    try {
      // first try: reading in an application
      diagrammer.setDataFile(new URL("file:resources/" + fileNameBase + ".xml"));
      showMessage("Reading " + fileNameBase + ".xml done.");
    } catch (Exception e) {
      showMessage(e.getMessage());
    }
  }

  /**
   * Loads a style file.
   * 
   * @param styleFileBase
   *          The base of the filename of the style file, excluding the path
   *          prefix and the extension suffix.
   */
  private void loadStyleSheet(String fileNameBase) {
    // load the main style file
    showMessage("Reading Sample" + fileNameBase + ".css ...");
    try {
      // first try: reading in an application
      diagrammer.setStyleSheet(new URL("file:resources/Sample" + fileNameBase + ".css"));
      showMessage("Reading Sample" + fileNameBase + ".css done.");
      // fit to contents
      diagrammer.fitToContents();
    } catch (Exception e) {
      showMessage(e.getMessage());
    }
  }

  /**
   * Randomize the node positions.
   */
  private void randomize() {
    // clear previous message
    showMessage("");

    // create a new random layout
    IlvRandomLayout randomLayout = new IlvRandomLayout();

    // save the old layout instance
    IlvGraphLayout oldLayout = getGraphLayout();

    // save the old constraints
    String constraints = saveConstraints(diagrammer.getEngine());

    // tell the diagram component to temporarily use the random layout, which
    // disables the tree layout.
    setGraphLayout(randomLayout);

    // perform the layout
    diagrammer.layoutAllNodes();

    // restore the old layout instance. Unfortunately
    // all constraints are lost.
    setGraphLayout(oldLayout);

    // therefore restore the constraints afterwards
    restoreConstraints(diagrammer.getEngine(), constraints);
  }

  /**
   * Saves the constraints to a string.
   */
  private String saveConstraints(IlvSDMEngine engine) {
    StringWriter writer = new StringWriter();
    engine.getNodeLayoutRenderer().writeConstraints(new PrintWriter(writer));
    try {
      writer.close();
    } catch (IOException ex) {
    }
    return writer.toString();
  }

  /**
   * Restores the constraints from a string.
   */
  private void restoreConstraints(IlvSDMEngine engine, String s) {
    StringReader reader = new StringReader(s);
    try {
      engine.getNodeLayoutRenderer().readConstraints(reader);
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Returns the graph layout currently used by the diagram component.
   */
  private IlvGraphLayout getGraphLayout() {
    IlvGraphLayoutRenderer renderer =
        (IlvGraphLayoutRenderer) diagrammer.getEngine().getNodeLayoutRenderer();
    return renderer == null ? null : renderer.getGraphLayout();
  }

  /**
   * Sets the graph layout currently used by the diagram component.
   */
  private void setGraphLayout(IlvGraphLayout layout) {
    IlvGraphLayoutRenderer renderer =
        (IlvGraphLayoutRenderer) diagrammer.getEngine().getNodeLayoutRenderer();
    if (renderer == null)
      return;
    renderer.setGraphLayout(layout);
  }

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

  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] args) {
    final HierarchicalConstraintsApp app = new HierarchicalConstraintsApp();
    app.init();

    // Since JDK 1.4, Sun recommends to make the component visible through
    // invokeLater.
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame frame = new JFrame("Tree Layout Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(600, 600);
        frame.getContentPane().add(app);
        frame.setVisible(true);
      }
    });
  }
}