/*
 * 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.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;

import ilog.views.IlvGraphic;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.diagrammer.application.IlvDiagrammerPropertySheet;
import ilog.views.diagrammer.application.IlvDiagrammerViewBar;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvSimplePopupMenu;
import ilog.views.util.IlvProductUtil;

/**
 * This example shows how to extend a Diagrammer project using the SDK.
 */
public class DiagrammerExtension extends JRootPane {
  private static final String projectFile = "data/map.idpr";

  private ResourceBundle bundle;

  // *****************************************
  // Creation and initialization of the demo:
  // *****************************************

  /**
   * The constructor: initializes the resource bundle used to get strings.
   */
  public DiagrammerExtension() {
    // 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);

    bundle = ResourceBundle.getBundle("extension");
  }

  /**
   * The main method called when the sample is run as an application.
   */
  public static void main(final String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        // Create an instance of the demo class.
        //
        DiagrammerExtension demo = new DiagrammerExtension();

        // Create the frame.
        //
        JFrame frame = new JFrame(demo.getString("FrameTitle"));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        try {
          // Initialize the demo's GUI inside the frame.
          //
          demo.init(frame.getContentPane(), new URL("file:./"));
        } catch (MalformedURLException e) {
          e.printStackTrace();
          System.exit(1);
        }

        // Show the frame.
        //
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
      }
    });
  }

  /**
   * Creates all the GUI elements and initializes the extension code.
   */
  public void init(Container contentPane, final URL baseURL) {
    // Create a simple menu bar:
    //
    JMenuBar menubar = new JMenuBar();

    JMenu fileMenu = new JMenu(getString("FileMenu"));
    fileMenu.add(IlvDiagrammerAction.createMenuItem(IlvDiagrammerAction.exit));
    menubar.add(fileMenu);

    JMenu helpMenu = new JMenu(getString("HelpMenu"));
    IlvDiagrammerAction.about.putValue(IlvDiagrammerAction.ABOUT_APPLICATION_NAME,
        getString("ApplicationName"));
    helpMenu.add(IlvDiagrammerAction.createMenuItem(IlvDiagrammerAction.about));
    menubar.add(helpMenu);

    ((JComponent) contentPane).getRootPane().setJMenuBar(menubar);

    // Create the IlvDiagrammer component:
    //
    final IlvDiagrammer diagrammer = new IlvDiagrammer();
    diagrammer.setScrollable(true);

    contentPane.add(diagrammer, BorderLayout.CENTER);

    // Add a standard Diagrammer toolbar for zooming/panning:
    //
    IlvDiagrammerViewBar viewBar = new IlvDiagrammerViewBar(JToolBar.VERTICAL);
    viewBar.removeAction(IlvDiagrammerAction.select);
    contentPane.add(viewBar, BorderLayout.LINE_START);

    // Initialize the pop-up menus.
    //
    initPopupMenus(diagrammer);

    // Create the latitude/longitude fields.
    //
    JPanel panel = createLatLongFields(diagrammer);
    contentPane.add(panel, BorderLayout.AFTER_LAST_LINE);

    // Load the project.
    //
    try {
      diagrammer.setDataFile(new URL(baseURL, projectFile));

      IlvRect bbox = diagrammer.getEngine().getGrapher().computeBBox(null);
      diagrammer.setPreferredSize(new Dimension((int) bbox.width + 20, (int) bbox.height + 20));
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  // *****************************************
  // Initialize of the pop-up menu:
  // *****************************************

  /**
   * This method creates the pop-up menus and registers them.
   */
  private void initPopupMenus(IlvDiagrammer diagrammer) {
    // register the view to handle pop-up menus
    IlvPopupMenuManager.registerView(diagrammer.getView());

    // Create the node pop-up menu
    JPopupMenu nodeMenu = createNodeLinkPopupMenu(diagrammer, true);

    // In the CSS, the node pop-up menu is specified in the following way
    // node {
    // ...
    // popupMenuName: "NodePopupMenu";
    // }
    //
    // Hence we must register a pop-up menu with the name "NodePopupMenu"
    IlvPopupMenuManager.registerMenu("NodePopupMenu", nodeMenu);

    // Create the link pop-up menu
    JPopupMenu linkMenu = createNodeLinkPopupMenu(diagrammer, false);

    // In the CSS, the link pop-up menu is specified in the following way
    // link {
    // ...
    // popupMenuName: "LinkPopupMenu";
    // }
    //
    // Hence we must register a pop-up menu with the name "LinkPopupMenu"
    IlvPopupMenuManager.registerMenu("LinkPopupMenu", linkMenu);

    // Register the white space menu for the main grapher.
    // Reason: when clicking in the white space, you are in fact clicking
    // on the main grapher
    JPopupMenu whitespaceMenu = createWhiteSpacePopupMenu(diagrammer);
    diagrammer.getEngine().getGrapher().setPopupMenu(whitespaceMenu);
  }

  /**
   * This method creates the pop-up menu for the nodes and links.
   */
  private JPopupMenu createNodeLinkPopupMenu(final IlvDiagrammer diagrammer, final boolean isNode) {
    IlvSimplePopupMenu popupMenu = new IlvSimplePopupMenu() {
      // select only the object that owns the pop-up menu before the pop-up
      // menu gets displayed
      Override
      protected void beforeDisplay(IlvPopupMenuContext context) {
        // always call super.beforeDisplay first
        super.beforeDisplay(context);
        // then the remaining menu preparation
        beforeNodeLinkPopupDisplay(this, context, diagrammer, isNode);
      }
    };
    // add a label to contain the title
    JLabel label = new JLabel();
    label.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 5));
    popupMenu.add(label);

    // add the remaining items
    popupMenu.addActions(
        (isNode
            ? "- | { DiagrammerExtension.StatusNormal | DiagrammerExtension.StatusWarning | DiagrammerExtension.StatusAlarm } |"
            : "") + "- | DiagrammerExtension.Properties",
        '|', '{', '}', bundle, 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);
            if (context == null)
              return;
            IlvGraphic g = context.getGraphic();
            // retrieve the object of the graphic
            Object obj = (g == null ? null : diagrammer.getEngine().getObject(g));

            if (obj == null)
              return;

            if (m.getText().equals(getString("StatusNormal")))
              diagrammer.setObjectProperty(obj, "status", "Normal");
            else if (m.getText().equals(getString("StatusWarning")))
              diagrammer.setObjectProperty(obj, "status", "Warning");
            else if (m.getText().equals(getString("StatusAlarm")))
              diagrammer.setObjectProperty(obj, "status", "Alarm");
            else if (m.getText().equals(getString("Properties"))) {
              // retrieve the position of the pop-up trigger
              IlvPoint p = context.getPoint();
              // show the properties
              showProperties(diagrammer, obj, (int) p.x, (int) p.y);
            }
          }
        });

    return popupMenu;
  }

  /**
   * The actions to be performed before displaying the pop-up menu for nodes or
   * links. This configures the pop-up menu to display the actual state.
   */
  private void beforeNodeLinkPopupDisplay(IlvSimplePopupMenu popupMenu, IlvPopupMenuContext context,
      IlvDiagrammer diagrammer, boolean isNode) {
    // deselect all
    diagrammer.deselectAll();
    // retrieve the graphic
    IlvGraphic g = context.getGraphic();
    // retrieve the object of the graphic
    Object obj = (g == null ? null : diagrammer.getEngine().getObject(g));
    // select the object
    if (obj != null)
      diagrammer.setSelected(obj, true);

    // the title label of the menu
    String title;

    if (isNode) {
      title = "";
      Object nodeName = diagrammer.getObjectProperty(obj, "name");
      title = MessageFormat.format(getString("NodeFormat"), new Object[] {nodeName});
    } else {
      // fill the label with the name of the link
      Object sourceName = diagrammer.getObjectProperty(diagrammer.getSourceNode(obj), "name");
      Object targetName = diagrammer.getObjectProperty(diagrammer.getTargetNode(obj), "name");
      title = MessageFormat.format(getString("LinkFormat"), new Object[] {sourceName, targetName});
    }

    // set the title label in the first entry of the menu
    JLabel label = (JLabel) popupMenu.getComponent(0);
    label.setText(title);

    // update the status
    if (isNode) {
      Object status = diagrammer.getObjectProperty(obj, "status");
      if ("Normal".equals(status))
        popupMenu.getItemForText(getString("StatusNormal")).setSelected(true);
      else if ("Warning".equals(status))
        popupMenu.getItemForText(getString("StatusWarning")).setSelected(true);
      else if ("Alarm".equals(status))
        popupMenu.getItemForText(getString("StatusAlarm")).setSelected(true);
    }
  }

  /**
   * This method creates the pop-up menu for the white space.
   */
  private JPopupMenu createWhiteSpacePopupMenu(final IlvDiagrammer diagrammer) {
    // Create the pop-up menu for the white space. This gets associated to
    // the top level manager. No actions associated with this pop-up menu.
    // It contains only a text.
    IlvSimplePopupMenu popupMenu = new IlvSimplePopupMenu() {
      // deselect all objects before the pop-up menu gets displayed
      Override
      protected void beforeDisplay(IlvPopupMenuContext context) {
        // always call super.beforeDisplay first
        super.beforeDisplay(context);
        // then deselect
        diagrammer.deselectAll();
      }
    };
    JLabel label = new JLabel(getString("NoObjectSelected"));
    label.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 5));
    popupMenu.add(label);

    return popupMenu;
  }

  private IlvDiagrammerPropertySheet propertySheet;
  private JDialog propertySheetDialog;

  /**
   * Displays a property sheet with all the properties of the selected object.
   */
  private void showProperties(final IlvDiagrammer diagrammer, Object object, int x, int y) {
    if (propertySheet == null) {
      Window window = SwingUtilities.getWindowAncestor(diagrammer);
      if (window instanceof Frame)
        propertySheetDialog = new JDialog((Frame) window);
      else
        propertySheetDialog = new JDialog();
      propertySheetDialog.setTitle(getString("PropertiesTitle"));
      propertySheetDialog.setModal(true);

      propertySheet = new IlvDiagrammerPropertySheet();
      propertySheetDialog.getContentPane().add(propertySheet);

      JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));
      JButton close = new JButton(getString("Close"));
      close.addActionListener(new ActionListener() {
        Override
        public void actionPerformed(ActionEvent e) {
          propertySheetDialog.setVisible(false);
        }
      });
      buttonPanel.add(close);
      propertySheetDialog.getContentPane().add(buttonPanel, BorderLayout.AFTER_LAST_LINE);

      propertySheetDialog.setSize(300, 300);
    }
    Point pos = diagrammer.getLocationOnScreen();
    propertySheetDialog.setLocation(pos.x + x + 20, pos.y + y + 20);
    propertySheetDialog.setVisible(true);
  }

  // *******************************************
  // Creation of the latitude/longitude fields:
  // *******************************************

  /**
   * Creates the latitude/longitude fields, and sets up a listener to update
   * them as the suer moves the mouse over the diagram.
   */
  private JPanel createLatLongFields(final IlvDiagrammer diagrammer) {
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));

    // Create the latitude field.
    //
    panel.add(new JLabel(getString("Latitude")));
    final JTextField latitudeField = new JTextField();
    latitudeField.setColumns(8);
    panel.add(latitudeField);

    // Create the longitude field.
    //
    panel.add(new JLabel(getString("Longitude")));
    final JTextField longitudeField = new JTextField();
    longitudeField.setColumns(8);
    panel.add(longitudeField);

    // Setup the listener.
    //
    diagrammer.getView().addMouseMotionListener(new MouseMotionAdapter() {
      Override
      public void mouseMoved(MouseEvent e) {
        if (ax == 0) {
          initTransform(diagrammer);
        }

        IlvPoint p = new IlvPoint(e.getX(), e.getY());
        diagrammer.getView().getTransformer().inverse(p);

        // p is now in manager coordinates: transform into
        // latitude and longitude.
        //
        double longitude = x0 + ax * p.x;
        double latitude = y0 + ay * p.y;

        longitudeField.setText(printDegrees(longitude, "W"));
        latitudeField.setText(printDegrees(latitude, "N"));
      }
    });

    return panel;
  }

  // Parameters to transform x/y into latitudes/longitudes:
  // longitude = x0 + ax * x;
  // latitude = y0 + ay * y;
  //
  float ax, ay, x0, y0;

  /**
   * Initializes the latitude/longitude -> x/y transformation parameters.
   */
  private void initTransform(final IlvDiagrammer diagrammer) {
    // We compute transformation parameters based on the
    // positions of the two first nodes.
    //
    Object node1 = null, node2 = null;
    Iterator<?> it = diagrammer.getAllObjects();
    while (it.hasNext()) {
      Object o = it.next();
      if (!diagrammer.isLink(o)) {
        if (node1 == null) {
          node1 = o;
        } else if (node2 == null) {
          node2 = o;
          break;
        }
      }
    }
    float x1 = Float.parseFloat((String) diagrammer.getObjectProperty(node1, "x"));
    float y1 = Float.parseFloat((String) diagrammer.getObjectProperty(node1, "y"));
    float lon1 = parseDegrees((String) diagrammer.getObjectProperty(node1, "longitude"));
    float lat1 = parseDegrees((String) diagrammer.getObjectProperty(node1, "latitude"));
    float x2 = Float.parseFloat((String) diagrammer.getObjectProperty(node2, "x"));
    float y2 = Float.parseFloat((String) diagrammer.getObjectProperty(node2, "y"));
    float lon2 = parseDegrees((String) diagrammer.getObjectProperty(node2, "longitude"));
    float lat2 = parseDegrees((String) diagrammer.getObjectProperty(node2, "latitude"));

    ax = (lon2 - lon1) / (x2 - x1);
    ay = (lat2 - lat1) / (y2 - y1);
    x0 = lon1 - ax * x1;
    y0 = lat1 - ay * y1;
  }

  /**
   * Parses a string like "45D30'N" into a floating-point number of degrees.
   */
  private float parseDegrees(String degreesString) {
    int di = degreesString.indexOf('D');
    int degrees = Integer.parseInt(degreesString.substring(0, di));
    int si = degreesString.indexOf('\'');
    int seconds = Integer.parseInt(degreesString.substring(di + 1, si));

    return degrees + (float) seconds / 60;
  }

  /**
   * Prints a floating-point number of degrees into a string like "45D30'N".
   */
  private String printDegrees(double degrees, String direction) {
    return String.valueOf((int) degrees) + "D" + String.valueOf((int) (degrees * 60) % 60) + "'"
        + direction;
  }

  // *****************
  // Utility methods:
  // *****************

  // Gets a string from the property file.
  //
  private String getString(String key) {
    return bundle.getString("DiagrammerExtension." + key);
  }
}