/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.editmode;

import com.google.common.base.Preconditions;
import com.google.security.zynamics.zylib.gui.zygraph.CDefaultLabelEventHandler;
import com.google.security.zynamics.zylib.gui.zygraph.IRawNodeAccessible;
import com.google.security.zynamics.zylib.gui.zygraph.editmode.CDefaultActionFactory;
import com.google.security.zynamics.zylib.gui.zygraph.editmode.IMouseState;
import com.google.security.zynamics.zylib.gui.zygraph.editmode.IMouseStateChange;
import com.google.security.zynamics.zylib.gui.zygraph.editmode.IStateActionFactory;
import com.google.security.zynamics.zylib.gui.zygraph.helpers.ISelectableNode;
import com.google.security.zynamics.zylib.gui.zygraph.helpers.IViewableNode;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.AbstractZyGraph;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.IZyEditModeListener;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.edges.ZyGraphEdge;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.editmode.helpers.CTooltipUpdater;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.helpers.IYNode;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.nodes.ZyGraphNode;

import y.base.Edge;
import y.base.Node;
import y.view.EditMode;
import y.view.MagnifierViewMode;

import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Default edit mode for ZyGraphs.
 * 
 * @param <NodeType> Type of all nodes in the ZyGraph
 * @param <EdgeType> Type of all edges in the ZyGraph
 */
public class ZyEditMode<NodeType extends ZyGraphNode<?> & IViewableNode & ISelectableNode & IYNode & IRawNodeAccessible, EdgeType extends ZyGraphEdge<?, ?, ?>>
    extends EditMode {
  /**
   * Provides the default actions that are executed on changes in mouse states.
   */
  private final IStateActionFactory<NodeType, EdgeType> m_actionFactory;

  /**
   * Provides the available mouse states.
   */
  private final CStateFactory<NodeType, EdgeType> m_stateFactory;

  /**
   * The current mouse state. This is transformed into a new state on every significant mouse event.
   */
  private IMouseState m_state;

  /**
   * The owning graph of the edit mode.
   */
  private final AbstractZyGraph<NodeType, EdgeType> m_graph;

  /**
   * Used to display the magnifying glass in the graph window.
   */
  private final MagnifierViewMode m_magifierViewMode = new MagnifierViewMode();

  /**
   * Listeners that are notified about import edit mode events.
   */
  private final List<IZyEditModeListener<NodeType, EdgeType>> m_listeners =
      new ArrayList<IZyEditModeListener<NodeType, EdgeType>>();

  private final CDefaultLabelEventHandler m_labelKeyHandler;

  private boolean m_inMouseMoved;

  private boolean m_inMouseDragged;

  private boolean m_inMousePressed;

  private boolean m_inMouseDraggedRight;

  /**
   * Creates a new edit mode object which is tied to the given graph.
   * 
   * @param graph The owning graph of the edit mode.
   */
  public ZyEditMode(final AbstractZyGraph<NodeType, EdgeType> graph) {
    m_graph = Preconditions.checkNotNull(graph, "Graph argument cannot be null");
    m_labelKeyHandler = createNodeKeyHandler(graph);
    m_actionFactory = createStateActionFactory();
    m_stateFactory = new CStateFactory<NodeType, EdgeType>(graph, m_listeners, m_actionFactory);
    m_state = m_stateFactory.createDefaultState();

    setDefaultBehaviour();
  }

  /**
   * Determines whether magnifying mode is active or not.
   * 
   * @return True, if magnifying mode is active. False, otherwise.
   */
  private boolean getMagnifyingMode() {
    // ESCA-JAVA0254: Can not improve the loop to for-each
    for (final Iterator<?> iter = m_graph.getView().getViewModes(); iter.hasNext();) {
      if (iter.next() == m_magifierViewMode) {
        return true;
      }
    }

    return false;
  }

  /**
   * Initializes the default behavior of the edit mode.
   */
  private void setDefaultBehaviour() {
    allowBendCreation(false);
    allowEdgeCreation(false);
    allowNodeCreation(false);

    allowMoveLabels(true);
    allowMovePorts(true);
    allowNodeEditing(true);
    allowMoving(true);
    allowMoveSelection(true);

    showEdgeTips(true);
    showNodeTips(true);

    setSelectionBoxMode(new CSelectionMode<NodeType>(m_graph));

    m_graph.getView().getCanvasComponent()
        .addMouseWheelListener(new ZyEditModeMouseWheelListener<NodeType, EdgeType>(m_graph));
  }

  protected CDefaultLabelEventHandler createNodeKeyHandler(
      final AbstractZyGraph<NodeType, EdgeType> graph) {
    return new CDefaultLabelEventHandler(graph);
  }

  protected IStateActionFactory<NodeType, EdgeType> createStateActionFactory() {
    return new CDefaultActionFactory<NodeType, EdgeType>();
  }

  @Override
  protected String getEdgeTip(final Edge edge) {
    return CTooltipUpdater.updateEdgeTooltip(m_graph, edge);
  }

  protected AbstractZyGraph<NodeType, EdgeType> getGraph() {
    return m_graph;
  }

  @Override
  protected String getNodeTip(final Node node) {
    return CTooltipUpdater.updateNodeTooltip(m_graph, node);
  }

  /**
   * Adds a listener object that is notified about changes in the edit mode.
   * 
   * @param listener The listener object to add.
   */
  public void addListener(final IZyEditModeListener<NodeType, EdgeType> listener) {
    m_listeners.add(listener);
  }

  public CDefaultLabelEventHandler getLabelEventHandler() {
    return m_labelKeyHandler;
  }

  public boolean isInMouseDragged() {
    return m_inMouseDragged;
  }

  public boolean isInMouseDraggedRight() {
    return m_inMouseDraggedRight;
  }

  public boolean isInMouseMoved() {
    return m_inMouseMoved;
  }

  public boolean isInMousePressed() {
    return m_inMousePressed;
  }

  @Override
  public void mouseDragged(final MouseEvent e) {
    Preconditions.checkNotNull(e, "Error: mouse event can not be null");

    final IMouseStateChange result = m_state.mouseDragged(e, m_graph);

    m_state = result.getNextState();

    if (result.notifyYFiles()) {
      m_inMouseDragged = true;
      try {
        super.mouseDragged(e);
      } finally {
        m_inMouseDragged = false;
      }
    }
  }

  @Override
  public void mouseDraggedRight(final double x, final double y) {
    m_inMouseDraggedRight = true;

    try {
      super.mouseDraggedLeft(x, y);
    } finally {
      m_inMouseDraggedRight = false;
    }
  }

  @Override
  public void mouseMoved(final double x, final double y) {
    m_inMouseMoved = true;
    try {
      super.mouseMoved(x, y);
    } finally {
      m_inMouseMoved = false;
    }
  }

  @Override
  public void mouseMoved(final MouseEvent e) {
    Preconditions.checkNotNull(e, "Error: mouse event can not be null");

    final IMouseStateChange result = m_state.mouseMoved(e, m_graph);

    m_state = result.getNextState();

    if (result.notifyYFiles()) {
      m_inMouseMoved = true;
      try {
        super.mouseMoved(e);
      } finally {
        m_inMouseMoved = false;
      }
    }
  }

  @Override
  public void mousePressed(final MouseEvent e) {
    final IMouseStateChange result = m_state.mousePressed(e, m_graph);

    m_state = result.getNextState();

    if (result.notifyYFiles()) {
      m_inMousePressed = true;
      try {
        super.mousePressed(e);
      } finally {
        m_inMousePressed = false;
      }
    }
  }

  @Override
  public void mouseReleased(final double x, final double y) {
    super.mouseReleased(x, y);
  }

  @Override
  public void mouseReleased(final MouseEvent e) {
    final IMouseStateChange result = m_state.mouseReleased(e, m_graph);

    m_state = result.getNextState();

    if (result.notifyYFiles()) {
      super.mouseReleased(e);
    }
  }

  /**
   * Enables or disables magnifying mode.
   * 
   * @param active True, to enable magnifying mode. False, to disable it.
   */
  public void setMagnifyingMode(final boolean active) {
    if (getMagnifyingMode() == active) {
      return;
    }

    if (active) {
      m_graph.getView().addViewMode(m_magifierViewMode);
    } else {
      m_graph.getView().removeViewMode(m_magifierViewMode);
    }
  }

  // ESCA-JAVA0059: Making this more visible
  @Override
  public double translateX(final int x) {
    return super.translateX(x);
  }

  // ESCA-JAVA0059: Making this more visible
  @Override
  public double translateY(final int y) {
    return super.translateY(y);
  }
}
