package demo.view.viewmode;

import demo.view.DemoBase;
import y.view.EditMode;
import y.view.Graph2D;
import y.view.MoveSelectionMode;
import y.view.BendList;
import y.view.ViewMode;
import y.view.BendCursor;
import y.base.Node;
import y.base.NodeList;
import y.base.EdgeCursor;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.JToolBar;
import javax.swing.JToggleButton;
import javax.swing.ButtonGroup;
import javax.swing.AbstractAction;
import javax.swing.Action;

/**
 * Demonstrates how to customize <code>EditMode</code>'s
 * <code>nodeDragged</code> behavior.
 */
public class CustomEditModeDemo extends DemoBase {
  private EditMode editMode;

  protected EditMode createEditMode() {
    editMode = new EditMode();
    return editMode;
  }

  protected JToolBar createToolBar() {
    final JToolBar jtb = super.createToolBar();
    jtb.addSeparator();

    // set up actions to switch between several policies for editing

    final MyEditMode defaultMode = new MyEditMode();
    defaultMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_DEFAULT);

    final MyEditMode createEdgeMode = new MyEditMode();
    createEdgeMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_CREATE_EDGE);

    final MyEditMode moveNodeMode = new MyEditMode();
    moveNodeMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_MOVE_NODE);

    final Action[] policySwitches = {
            new AbstractAction("Default") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = defaultMode;
                view.addViewMode(editMode);
              }
            },
            new AbstractAction("Create Edge") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = createEdgeMode;
                view.addViewMode(editMode);
              }
            },
            new AbstractAction("Move Node") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = moveNodeMode;
                view.addViewMode(editMode);
              }
            }
    };
    final ButtonGroup group = new ButtonGroup();
    for (int i = 0; i < policySwitches.length; ++i) {
      final JToggleButton jb = new JToggleButton(policySwitches[i]);
      jb.setSelected(i == 0);
      group.add(jb);
      jtb.add(jb);
    }

    return jtb;
  }

  public static void main( String[] args ) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        initLnF();
        (new CustomEditModeDemo()).start();
      }
    });
  }

  /**
   * Custom <code>EditMode</code> that uses policies to determine its
   * <code>nodeDragged</code> behavior.
   */
  private static final class MyEditMode extends EditMode {
    /**
     * Node dragged behavior policy for the default behavior inherited from
     * EditMode.
     */
    public static final byte NODE_DRAG_POLICY_DEFAULT = 0;
    /**
     * Node dragged behavior policy that triggers <code>CreateEdgeMode</code>
     * no matter of the dragged node was selected or not.
     */
    public static final byte NODE_DRAG_POLICY_CREATE_EDGE = 1;
    /**
     * Node dragged behavior policy that triggers <code>MoveSelectionMode</code>
     * no matter of the dragged node was selected or not.
     */
    public static final byte NODE_DRAG_POLICY_MOVE_NODE = 2;

    private byte nodeDragPolicy;

    public byte getNodeDragPolicy() {
      return nodeDragPolicy;
    }

    public void setNodeDragPolicy( final byte policy ) {
      switch (policy) {
        case NODE_DRAG_POLICY_DEFAULT:
        case NODE_DRAG_POLICY_CREATE_EDGE:
        case NODE_DRAG_POLICY_MOVE_NODE:
          this.nodeDragPolicy = policy;
          break;
        default:
          throw new IllegalArgumentException("unsupported policy: " + policy);
      }
    }

    /**
     * Overwritten to behave according to the current node drag policy.
     */
    protected void nodeDragged(
            final Graph2D graph,
            final Node node,
            final boolean wasSelected,
            final double x,
            final double y,
            final boolean firstDrag
    ) {
      switch (getNodeDragPolicy()) {
        case NODE_DRAG_POLICY_DEFAULT:
          super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          break;
        case NODE_DRAG_POLICY_CREATE_EDGE:
          if (doAllowEdgeCreation() && getCreateEdgeMode() != null) {
            setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
          } else {
            super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          }
          break;
        case NODE_DRAG_POLICY_MOVE_NODE:
          if (firstDrag && getMoveSelectionMode() != null) {
            if (!wasSelected && getMoveSelectionMode() instanceof MyMoveSelectionMode) {
              ((MyMoveSelectionMode) getMoveSelectionMode()).setDraggedNode(node);
            }
            setChild(getMoveSelectionMode(), lastPressEvent, lastDragEvent);
          } else {
            super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          }
          break;
      }
    }

    protected ViewMode createMoveSelectionMode() {
      return new MyMoveSelectionMode();
    }
  }

  /**
   * Custom <code>MoveSelectionMode</code> that supports single unselected
   * nodes, too.
   */
  private static final class MyMoveSelectionMode extends MoveSelectionMode {
    private Node draggedNode;

    public Node getDraggedNode() {
      return draggedNode;
    }

    public void setDraggedNode( final Node draggedNode ) {
      this.draggedNode = draggedNode;
    }

    /**
     * Overwritten to ensure that only the node that is marked as
     * <code>draggedNode</code> is moved if there is such a node.
     */
    protected NodeList getNodesToBeMoved() {
      if (draggedNode == null) {
        return super.getNodesToBeMoved();
      } else {
        return new NodeList(draggedNode);
      }
    }

    /**
     * Overwritten to ensure that only the node that is marked as
     * <code>draggedNode</code> is moved if there is such a node.
     */
    protected BendList getBendsToBeMoved() {
      if (draggedNode == null) {
        return super.getBendsToBeMoved();
      } else {
        final BendList bends = new BendList();
        for (EdgeCursor ec = draggedNode.inEdges(); ec.ok(); ec.next()) {
          if (ec.edge().source() == draggedNode) {
            for (BendCursor bc = view.getGraph2D().getRealizer(ec.edge()).bends(); bc.ok(); bc.next()) {
              bends.add(bc.bend());
            }
          }
        }
        return bends;
      }
    }

    /**
     * Overwritten to clear the <code>draggedNode</code> property upon
     * parent mode reactivation to prevent stale data when the mode
     * is activated the next time.
     */
    public void reactivateParent() {
      draggedNode = null;
      super.reactivateParent();
    }
  }
}
