package com.mxgraph.swing.handler;

import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxMouseAdapter;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;

/**
 * Basic example of implementing a handler for rotation. This can be used as follows:
 * 
 * new mxRotationHandler(graphComponent)
 * 
 * Note that the Java core does actually not support rotation for the selection handles, perimeter
 * points etc. Feel free to contribute a fix!
 */
public class mxRotationHandler extends mxMouseAdapter {
  /**
   * 
   */
  public static ImageIcon ROTATE_ICON = null;

  /**
   * Loads the collapse and expand icons.
   */
  static {
    ROTATE_ICON =
        new ImageIcon(mxRotationHandler.class.getResource("/com/mxgraph/swing/images/rotate.gif"));
  }

  /**
   * 
   */
  private static double PI4 = Math.PI / 4;

  /**
   * Reference to the enclosing graph component.
   */
  protected mxGraphComponent graphComponent;

  /**
   * Specifies if this handler is enabled. Default is true.
   */
  protected boolean enabled = true;

  /**
   * 
   */
  protected JComponent handle;

  /**
   * 
   */
  protected mxCellState currentState;

  /**
   * 
   */
  protected double initialAngle;

  /**
   * 
   */
  protected double currentAngle;

  /**
   * 
   */
  protected Point first;

  /**
   * Constructs a new rotation handler.
   */
  public mxRotationHandler(mxGraphComponent graphComponent) {
    this.graphComponent = graphComponent;
    graphComponent.addMouseListener(this);
    handle = createHandle();

    // Installs the paint handler
    graphComponent.addListener(mxEvent.AFTER_PAINT, new mxIEventListener() {
      public void invoke(Object sender, mxEventObject evt) {
        Graphics g = (Graphics) evt.getProperty("g");
        paint(g);
      }
    });

    // Listens to all mouse events on the rendering control
    graphComponent.getGraphControl().addMouseListener(this);
    graphComponent.getGraphControl().addMouseMotionListener(this);

    // Needs to catch events because these are consumed
    handle.addMouseListener(this);
    handle.addMouseMotionListener(this);
  }

  /**
   * 
   */
  public mxGraphComponent getGraphComponent() {
    return graphComponent;
  }

  /**
   * 
   */
  public boolean isEnabled() {
    return enabled;
  }

  /**
   * 
   */
  public void setEnabled(boolean value) {
    enabled = value;
  }

  /**
   * 
   */
  protected JComponent createHandle() {
    JLabel label = new JLabel(ROTATE_ICON);
    label.setSize(ROTATE_ICON.getIconWidth(), ROTATE_ICON.getIconHeight());
    label.setOpaque(false);

    return label;
  }

  /**
   * 
   */
  public boolean isStateHandled(mxCellState state) {
    return graphComponent.getGraph().getModel().isVertex(state.getCell());
  }

  /**
   * 
   */
  public void mousePressed(MouseEvent e) {
    if (currentState != null && handle.getParent() != null
        && e.getSource() == handle /* mouse hits handle */) {
      start(e);
      e.consume();
    }
  }

  /**
   * 
   */
  public void start(MouseEvent e) {
    initialAngle = mxUtils.getDouble(currentState.getStyle(), mxConstants.STYLE_ROTATION)
        * mxConstants.RAD_PER_DEG;
    currentAngle = initialAngle;
    first = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
        graphComponent.getGraphControl());

    if (!graphComponent.getGraph().isCellSelected(currentState.getCell())) {
      graphComponent.selectCellForEvent(currentState.getCell(), e);
    }
  }

  /**
   * 
   */
  public void mouseMoved(MouseEvent e) {
    if (graphComponent.isEnabled() && isEnabled()) {
      if (handle.getParent() != null && e.getSource() == handle /* mouse hits handle */) {
        graphComponent.getGraphControl().setCursor(new Cursor(Cursor.HAND_CURSOR));
        e.consume();
      } else if (currentState == null || !currentState.getRectangle().contains(e.getPoint())) {
        mxCellState eventState = graphComponent.getGraph().getView()
            .getState(graphComponent.getCellAt(e.getX(), e.getY(), false));

        mxCellState state = null;

        if (eventState != null && isStateHandled(eventState)) {
          state = eventState;
        }

        if (currentState != state) {
          currentState = state;

          if (currentState == null && handle.getParent() != null) {
            handle.setVisible(false);
            handle.getParent().remove(handle);
          } else if (currentState != null) {
            if (handle.getParent() == null) {
              // Adds component for rendering the handles (preview is separate)
              graphComponent.getGraphControl().add(handle, 0);
              handle.setVisible(true);
            }

            handle.setLocation(
                (int) (currentState.getX() + currentState.getWidth() - handle.getWidth() - 4),
                (int) (currentState.getY() + currentState.getHeight() - handle.getWidth() - 4));
          }
        }
      }
    }
  }

  /**
   * 
   */
  public void mouseDragged(MouseEvent e) {
    if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed() && first != null) {
      mxRectangle dirty =
          mxUtils.getBoundingBox(currentState, currentAngle * mxConstants.DEG_PER_RAD);
      Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
          graphComponent.getGraphControl());

      double cx = currentState.getCenterX();
      double cy = currentState.getCenterY();
      double dx = pt.getX() - cx;
      double dy = pt.getY() - cy;
      double c = Math.sqrt(dx * dx + dy * dy);

      currentAngle = ((pt.getX() > cx) ? -1 : 1) * Math.acos(dy / c) + PI4 + initialAngle;

      dirty.add(mxUtils.getBoundingBox(currentState, currentAngle * mxConstants.DEG_PER_RAD));
      dirty.grow(1);

      // TODO: Compute dirty rectangle and repaint
      graphComponent.getGraphControl().repaint(dirty.getRectangle());
      e.consume();
    } else if (handle.getParent() != null) {
      handle.getParent().remove(handle);
    }
  }

  /**
   * 
   */
  public void mouseReleased(MouseEvent e) {
    if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed() && first != null) {
      double deg = 0;
      Object cell = null;

      if (currentState != null) {
        cell = currentState.getCell();
        /*
         * deg = mxUtils.getDouble(currentState.getStyle(), mxConstants.STYLE_ROTATION);
         */
      }

      deg += currentAngle * mxConstants.DEG_PER_RAD;
      boolean willExecute = cell != null && first != null;

      // TODO: Call reset before execute in all handlers that
      // offer an execute method
      reset();

      if (graphComponent.isEnabled() && isEnabled() && !e.isConsumed() && willExecute) {
        graphComponent.getGraph().setCellStyles(mxConstants.STYLE_ROTATION, String.valueOf(deg),
            new Object[] {cell});

        graphComponent.getGraphControl().repaint();

        e.consume();
      }
    }

    currentState = null;
  }

  /**
   * 
   */
  public void reset() {
    if (handle.getParent() != null) {
      handle.getParent().remove(handle);
    }

    mxRectangle dirty = null;

    if (currentState != null && first != null) {
      dirty = mxUtils.getBoundingBox(currentState, currentAngle * mxConstants.DEG_PER_RAD);
      dirty.grow(1);
    }

    currentState = null;
    currentAngle = 0;
    first = null;

    if (dirty != null) {
      graphComponent.getGraphControl().repaint(dirty.getRectangle());
    }
  }

  /**
   *
   */
  public void paint(Graphics g) {
    if (currentState != null && first != null) {
      Rectangle rect = currentState.getRectangle();
      double deg = currentAngle * mxConstants.DEG_PER_RAD;

      if (deg != 0) {
        ((Graphics2D) g).rotate(Math.toRadians(deg), currentState.getCenterX(),
            currentState.getCenterY());
      }

      mxUtils.setAntiAlias((Graphics2D) g, true, false);
      g.drawRect(rect.x, rect.y, rect.width, rect.height);
    }
  }

}