package demo.view.hierarchy;

import y.view.Arrow;
import y.view.Graph2D;
import y.view.NodeLabel;
import y.view.NodeRealizer;
import y.view.ShapeNodeRealizer;
import y.view.SizeConstraintProvider;
import y.view.GenericNodeRealizer;
import y.view.ShapeNodePainter;
import y.view.EditMode;
import y.view.PopupMode;
import y.view.hierarchy.DefaultHierarchyGraphFactory;
import y.view.hierarchy.DefaultNodeChangePropagator;
import y.view.hierarchy.GroupNodeRealizer;
import y.view.hierarchy.HierarchyEditMode;
import y.view.hierarchy.GenericGroupNodeRealizer;
import y.base.GraphFactory;
import y.base.Node;
import y.geom.YDimension;
import y.module.LaunchModuleAction;

import javax.swing.JPopupMenu;
import javax.swing.AbstractAction;
import javax.swing.JMenuBar;
import javax.swing.MenuElement;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.Action;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.Map;

import demo.module.HierarchicLayoutModule;

/**
 * This program demonstrates how NodeRealizers and especially GroupNodeRealizers can be customized to keep to given
 * size constraints.
 *
 * One can determine that the nodes shall not be smaller than a given minimum or bigger than a given maximum node size.
 * Resizing these nodes interactively using HotSpots will keep to these restrictions as well as layout algorithms.
 *
 * This approach is especially quite interesting when using group nodes in hierarchically organized graphs. One can
 * for example determine that a group node shall never be smaller than an according label. Layout algorithms will then
 * keep to these settings even if the inner graph of such a group node needs less space.
 */
public class NodeRealizerSizeConstraintsDemo extends HierarchyDemo {
  private boolean initialized;

  protected final Color GROUP_NODE_COLOR = new Color(248, 236, 201);
  protected final Color FOLDER_NODE_COLOR = new Color(248, 236, 201);
  protected final int INITIAL_GROUP_WIDTH = 100;
  protected final int INITIAL_GROUP_HEIGHT = 100;
  protected final int INITIAL_FOLDER_WIDTH = 100;
  protected final int INITIAL_FOLDER_HEIGHT = 60;

  /** Instantiates this demo. Builds the GUI. */
  public NodeRealizerSizeConstraintsDemo() {
    Graph2D graph = view.getGraph2D();

    //add arrows to the default edges of the graph
    graph.getDefaultEdgeRealizer().setTargetArrow(Arrow.STANDARD);

    //set custom node realizer as default
    graph.setDefaultNodeRealizer(createGenericNodeRealizer());
    //one could also use a custom ShapeNodeRealizer instead of a GenericNodeRealizer
//    graph.setDefaultNodeRealizer(createCustomShapeNodeRealizer());

    //set some customized node realizers for group and folder nodes
    initGroupNodeRealizes();

    //propagates text label changes on nodes as change events on the hierarchy.
    graph.addGraph2DListener(new DefaultNodeChangePropagator());

    initialized = true;
    loadInitialGraph();
    graph.updateViews();
  }

  protected EditMode createEditMode() {
    EditMode mode = new HierarchyEditMode();
    //add hierarchy actions to the views popup menu
    mode.setPopupMode(new HierarchicPopupMode());
    return mode;
  }


  protected JMenuBar createMenuBar() {
    JMenuBar menuBar = super.createMenuBar();
    //remove some unnecessary tools entries
    for(int i = 0; i < menuBar.getMenuCount(); i++){
      JMenu menu = menuBar.getMenu(i);
      if("Tools".equals(menu.getText())){
        menu.removeAll();
        menu.add(new JMenuItem(new FoldComponentsAction()));
        menu.add(new JMenuItem(new FoldSubtreesAction()));
        menu.add(new JMenuItem(new UnfoldAllAction()));

        menu.addSeparator();                                                    //@VIEW_EXCLUSION@
        Action a = new LaunchModuleAction(view, new HierarchicLayoutModule());  //@VIEW_EXCLUSION@
        a.putValue(Action.NAME, "Hierarchical Layout");                         //@VIEW_EXCLUSION@
        menu.add(a);                                                            //@VIEW_EXCLUSION@
      }
    }
    return menuBar;
  }

  protected void doConsiderNodeLabels(Node node, boolean doConsider) {
    Graph2D graph = view.getGraph2D();
    NodeRealizer realizer = graph.getRealizer(node);
    if(realizer instanceof GroupNodeRealizer) {
      GroupNodeRealizer gnr = (GroupNodeRealizer) realizer;
      gnr.setConsiderNodeLabelSize(doConsider);
    }
  }

  /**
   * create a custom ShapeNodeRealizer
   * @return a custom ShapeNodeRealizer
   */
  private NodeRealizer createCustomShapeNodeRealizer() {
    return new LabelAwareShapeNodeRealizer();
  }

  /**
   * This custom GenericNoderealizer reacts on label bounds changes of its first label and resizes the node accordingly.
   * Furthermor it provides a SizeConstraintProvider that is used to determine minimum and maximum node sizes. This
   * provider is for example used for interactive HotSpot resizing.
   * @return a custom GenericNodeRealizer
   */
  private NodeRealizer createGenericNodeRealizer() {
    final int MAX_NODE_HEIGHT = 100;
    final int MAX_NODE_WIDTH = 300;

    GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
    Map implementationsMap = factory.createDefaultConfigurationMap();

    ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.RECT);
    implementationsMap.put(GenericNodeRealizer.Painter.class, painter);

    GenericNodeRealizer.GenericSizeConstraintProvider scp = new GenericNodeRealizer.GenericSizeConstraintProvider() {
      public YDimension getMinimumSize(NodeRealizer context) {
        return new YDimension(Math.max(1, context.getLabel().getWidth()), Math.max(1, context.getLabel().getHeight()));
      }

      public YDimension getMaximumSize(NodeRealizer context) {
        return new YDimension(MAX_NODE_WIDTH, MAX_NODE_HEIGHT);
      }
    };
    implementationsMap.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, scp);

    implementationsMap.put(GenericNodeRealizer.LabelBoundsChangedHandler.class,
        new GenericNodeRealizer.LabelBoundsChangedHandler() {
          public void labelBoundsChanged(NodeRealizer context, NodeLabel label) {
            if (label == context.getLabel()) {//only resize on bounds changes of the first label
              context.setSize(Math.max(30, label.getWidth()), Math.max(30, label.getHeight()));
            }
          }
        });

    String configuration = "Label Aware Node Realizer";
    factory.addConfiguration(configuration, implementationsMap);
    return new GenericNodeRealizer(configuration);
  }

  private void initGroupNodeRealizes() {
    GraphFactory graphFactory = hierarchy.getGraphFactory();
    if (graphFactory instanceof DefaultHierarchyGraphFactory) {
      DefaultHierarchyGraphFactory hierarchyGraphFactory = ((DefaultHierarchyGraphFactory) graphFactory);
      GroupNodeRealizer group = new GroupNodeRealizer();
      //this will make sure that the label will be taken into account when calculating a minimum size of this node
      group.setConsiderNodeLabelSize(true);
      group.setSize(INITIAL_GROUP_WIDTH, INITIAL_GROUP_HEIGHT);
      group.setFillColor(GROUP_NODE_COLOR);
      group.setGroupClosed(false);
      hierarchyGraphFactory.setDefaultGroupNodeRealizer(group);

      GroupNodeRealizer folder = new GroupNodeRealizer();
      //this will make sure that the label will be taken into account when calculating a minimum size of this node
      folder.setConsiderNodeLabelSize(true);
      folder.setSize(INITIAL_FOLDER_WIDTH, INITIAL_FOLDER_HEIGHT);
      folder.setFillColor(FOLDER_NODE_COLOR);
      folder.setGroupClosed(true);
      hierarchyGraphFactory.setDefaultGroupNodeRealizer(folder);
    }
  }

  protected void loadInitialGraph() {
    //make sure to only load the graph, when our custom realizers have been initialized.
    if(initialized){
      loadGraph("resource/hierarchy2.gml");
    }
  }

  /**
   * Launches this demo.
   */
  public static void main(String[] args) {
    initLnF();
    (new NodeRealizerSizeConstraintsDemo()).start("Size Constraint Provider Demo");
  }

  /**
   * This custom ShapeNodeRealizer reacts on label bounds changes of its first label and resizes the node accordingly.
   * Furthermor it provides a SizeConstraintProvider that is used to determine minimum and maximum node sizes. This
   * provider is for example used for interactive HotSpot resizing.
   */
  public static class LabelAwareShapeNodeRealizer extends ShapeNodeRealizer {
    protected final double MAX_NODE_HEIGHT = Double.MAX_VALUE;
    protected final double MAX_NODE_WIDTH = Double.MAX_VALUE;

    public LabelAwareShapeNodeRealizer() {
      super(RECT, 0, 0, "");
    }

    public LabelAwareShapeNodeRealizer(NodeRealizer nr) {
      super(nr);
    }

    public NodeRealizer createCopy(NodeRealizer nr) {
      return new LabelAwareShapeNodeRealizer(nr);
    }

    public SizeConstraintProvider getSizeConstraintProvider() {
      //provide the minimum and maximum sizes this realizer shall have.
      //in this case the minimum size is determined by the first node label.
      return new SizeConstraintProvider.Default(Math.max(1, getLabel().getWidth()), Math.max(1, getLabel().getHeight()),
          MAX_NODE_WIDTH, MAX_NODE_HEIGHT);
    }

    protected void labelBoundsChanged(NodeLabel label) {
      if (label == getLabel()) {//only resize on bounds changes of the first label
        setSize(Math.max(30, label.getWidth()), Math.max(30, label.getHeight()));
      }
    }
  }

  public static class SizeConstraintShapeNodeRealizer extends ShapeNodeRealizer {
    protected final double MIN_NODE_HEIGHT = 10;
    protected final double MIN_NODE_WIDTH = 10;
    protected final double MAX_NODE_HEIGHT = 200;
    protected final double MAX_NODE_WIDTH = 200;

    public SizeConstraintShapeNodeRealizer() {
      super(RECT, 0, 0, "");
    }

    public SizeConstraintShapeNodeRealizer(NodeRealizer nr) {
      super(nr);
    }

    public NodeRealizer createCopy(NodeRealizer nr) {
      return new SizeConstraintShapeNodeRealizer(nr);
    }

    public SizeConstraintProvider getSizeConstraintProvider() {
      //provide the minimum and maximum sizes this realizer shall have.
      return new SizeConstraintProvider.Default(MIN_NODE_WIDTH, MIN_NODE_HEIGHT, MAX_NODE_WIDTH, MAX_NODE_HEIGHT);
    }
  }

  /**
   * provides the context sensitive popup menus
   */
  class HierarchicPopupMode extends PopupMode
  {
    public JPopupMenu getPaperPopup(double x, double y)
    {
      return addFolderPopupItems(new JPopupMenu(), x,y, null, false);
    }

    public JPopupMenu getNodePopup(Node v)
    {
      Graph2D graph = getGraph2D();
      return addFolderPopupItems(new JPopupMenu(),
                                 graph.getCenterX(v),
                                 graph.getCenterY(v),
                                 v, true);
    }

    public JPopupMenu getSelectionPopup(double x, double y)
    {
      return addFolderPopupItems(new JPopupMenu(),x,y, null, getGraph2D().selectedNodes().ok());
    }

    JPopupMenu addFolderPopupItems(JPopupMenu pm, double x, double y, Node node, boolean selected)
    {
      AbstractAction action;
      action = new GroupSelectionAction(x, y);
      pm.add(action);
      action = new UngroupSelectionAction();
      pm.add(action);
      action = new CloseGroupAction(node);
      action.setEnabled(node != null && hierarchy.isGroupNode(node));
      pm.add(action);
      pm.addSeparator();
      action = new CreateFolderNodeAction(getGraph2D(),x,y);
      action.setEnabled(node == null);
      pm.add(action);
      action = new FoldSelectionAction();
      action.setEnabled(selected);
      pm.add(action);
      action = new UnfoldSelectionAction();
      action.setEnabled(selected && !hierarchy.isRootGraph(getGraph2D()));
      pm.add(action);
      action = new ExtractFolderAction(node);
      action.setEnabled(node != null && hierarchy.isFolderNode(node));
      pm.add(action);
      action = new OpenFolderAction(node);
      action.setEnabled(node != null && hierarchy.isFolderNode(node));
      pm.add(action);

      Graph2D graph = view.getGraph2D();
      if(graph != null && node != null){
        NodeRealizer realizer = graph.getRealizer(node);
        if(realizer instanceof GroupNodeRealizer) {
          GroupNodeRealizer gnr = (GroupNodeRealizer) realizer;
          if(gnr.isConsiderNodeLabelSize()){
            action = new LabelConsiderationAction("Do not consider Node Label", node, false);
          } else {
            action = new LabelConsiderationAction("Consider Node Label", node, true);
          }
          pm.addSeparator();
          pm.add(action);
        }
      }
      return pm;
    }
  }

  class LabelConsiderationAction extends AbstractAction
  {
    Node node;
    private final boolean enableLabelConsideration;

    LabelConsiderationAction(String name, Node node, boolean doConsider) {
      super(name);
      this.node = node;
      this.enableLabelConsideration = doConsider;
    }

    public void actionPerformed(ActionEvent e){
      doConsiderNodeLabels(node, enableLabelConsideration);
    }
  }

}
