/*
 * 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.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvGraphic;
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.diagrammer.internal.AppletStringPropertyEditor;
import ilog.views.util.beans.IlvPropertyEditorManager;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvSimplePopupMenu;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.swing.IlvSwingUtil;

import java.awt.*;
import java.awt.event.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.ResourceBundle;

import javax.swing.*;

/**
 * This example shows how to extend a Diagrammer project using the SDK.
 */ 
public class DiagrammerExtension extends JApplet
{
  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 Rogue Wave 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() {
      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:./"), false);
        } catch (MalformedURLException e) {
          e.printStackTrace();
          System.exit(1);
        }
        
        // Show the frame.
        //
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
      }
    });
  }
  
  /**
   * The init method called when the sample is run as an applet.
   */
  public void init()
  {
    // Initialize the demo's GUI directly inside the applet panel.
    //
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init(getContentPane(), getDocumentBase(), true);
      }
    });
  }
  
  /**
   * This is the common code between the application and applet cases
   * that creates all the GUI elements and initializes the extension code.
   */
  public void init(Container contentPane, final URL baseURL, boolean applet)
  {
    // Create a simple menu bar:
    //
    JMenuBar menubar = new JMenuBar();
    if(!applet){
      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();
    }
  }
  
  /**
   * Called when the applet is destroyed.
   */ 
  public void destroy()
  {
    // This method is intended to work around 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();
  }

  //*****************************************
  // 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
        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() {
         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
        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(){
        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(){
      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.
        //
        float longitude = x0 + ax * p.x;
        float 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(float 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);
  }
}