/**
 * $Id: mxCellHandler.java,v 1.1 2012/11/15 13:26:44 gaudenz Exp $
 * Copyright (c) 2008-2012, JGraph Ltd
 */
package com.mxgraph.swing.handler;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxSwingConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

/**
 * @author Administrator
 */
public class mxCellHandler {
  /**
   * Reference to the enclosing graph component.
   */
  protected mxGraphComponent graphComponent;

  /**
   * Holds the cell state associated with this handler.
   */
  protected mxCellState state;

  /**
   * Holds the rectangles that define the handles.
   */
  protected Rectangle[] handles;

  /**
   * Specifies if the handles should be painted. Default is true.
   */
  protected boolean handlesVisible = true;

  /**
   * Holds the bounding box of the handler.
   */
  protected transient Rectangle bounds;

  /**
   * Holds the component that is used for preview.
   */
  protected transient JComponent preview;

  /**
   * Holds the start location of the mouse gesture.
   */
  protected transient Point first;

  /**
   * Holds the index of the handle that was clicked.
   */
  protected transient int index;

  /**
   * Constructs a new cell handler for the given cell state.
   *
   * @param graphComponent Enclosing graph component.
   * @param state          Cell state for which the handler is created.
   */
  public mxCellHandler(mxGraphComponent graphComponent, mxCellState state) {
    this.graphComponent = graphComponent;
    refresh(state);
  }

  /**
   *
   */
  public boolean isActive() {
    return first != null;
  }

  /**
   * Refreshes the cell handler.
   */
  public void refresh(mxCellState state) {
    this.state = state;
    handles = createHandles();
    mxGraph graph = graphComponent.getGraph();
    mxRectangle tmp = graph.getBoundingBox(state.getCell());

    if (tmp != null) {
      bounds = tmp.getRectangle();

      if (handles != null) {
        for (int i = 0; i < handles.length; i++) {
          if (isHandleVisible(i)) {
            bounds.add(handles[i]);
          }
        }
      }
    }
  }

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

  /**
   * Returns the cell state that is associated with this handler.
   */
  public mxCellState getState() {
    return state;
  }

  /**
   * Returns the index of the current handle.
   */
  public int getIndex() {
    return index;
  }

  /**
   * Returns the bounding box of this handler.
   */
  public Rectangle getBounds() {
    return bounds;
  }

  /**
   * Returns true if the label is movable.
   */
  public boolean isLabelMovable() {
    mxGraph graph = graphComponent.getGraph();
    String label = graph.getLabel(state.getCell());

    return graph.isLabelMovable(state.getCell()) && label != null && label.length() > 0;
  }

  /**
   * Returns true if the handles should be painted.
   */
  public boolean isHandlesVisible() {
    return handlesVisible;
  }

  /**
   * Specifies if the handles should be painted.
   */
  public void setHandlesVisible(boolean handlesVisible) {
    this.handlesVisible = handlesVisible;
  }

  /**
   * Returns true if the given index is the index of the last handle.
   */
  public boolean isLabel(int index) {
    return index == getHandleCount() - 1;
  }

  /**
   * Creates the rectangles that define the handles.
   */
  protected Rectangle[] createHandles() {
    return null;
  }

  /**
   * Returns the number of handles in this handler.
   */
  protected int getHandleCount() {
    return (handles != null) ? handles.length : 0;
  }

  /**
   * Hook for subclassers to return tooltip texts for certain points on the
   * handle.
   */
  public String getToolTipText(MouseEvent e) {
    return null;
  }

  /**
   * Returns the index of the handle at the given location.
   *
   * @param x X-coordinate of the location.
   * @param y Y-coordinate of the location.
   * @return Returns the handle index for the given location.
   */
  public int getIndexAt(int x, int y) {
    if (handles != null && isHandlesVisible()) {
      int tol = graphComponent.getTolerance();
      Rectangle rect = new Rectangle(x - tol / 2, y - tol / 2, tol, tol);

      for (int i = handles.length - 1; i >= 0; i--) {
        if (isHandleVisible(i) && handles[i].intersects(rect)) {
          return i;
        }
      }
    }

    return -1;
  }

  /**
   * Processes the given event.
   */
  public void mousePressed(MouseEvent e) {
    if (!e.isConsumed()) {
      int tmp = getIndexAt(e.getX(), e.getY());

      if (!isIgnoredEvent(e) && tmp >= 0 && isHandleEnabled(tmp)) {
        graphComponent.stopEditing(true);
        start(e, tmp);
        e.consume();
      }
    }
  }

  /**
   * Processes the given event.
   */
  public void mouseMoved(MouseEvent e) {
    if (!e.isConsumed() && handles != null) {
      int index = getIndexAt(e.getX(), e.getY());

      if (index >= 0 && isHandleEnabled(index)) {
        Cursor cursor = getCursor(e, index);

        if (cursor != null) {
          graphComponent.getGraphControl().setCursor(cursor);
          e.consume();
        }
        else {
          graphComponent.getGraphControl().setCursor(new Cursor(Cursor.HAND_CURSOR));
        }
      }
    }
  }

  /**
   * Processes the given event.
   */
  public void mouseDragged(MouseEvent e) {
    // empty
  }

  /**
   * Processes the given event.
   */
  public void mouseReleased(MouseEvent e) {
    reset();
  }

  /**
   * Starts handling a gesture at the given handle index.
   */
  public void start(MouseEvent e, int index) {
    this.index = index;
    first = e.getPoint();
    preview = createPreview();

    if (preview != null) {
      graphComponent.getGraphControl().add(preview, 0);
    }
  }

  /**
   * Returns true if the given event should be ignored.
   */
  protected boolean isIgnoredEvent(MouseEvent e) {
    return graphComponent.isEditEvent(e);
  }

  /**
   * Creates the preview for this handler.
   */
  protected JComponent createPreview() {
    return null;
  }

  /**
   * Resets the state of the handler and removes the preview.
   */
  public void reset() {
    if (preview != null) {
      preview.setVisible(false);
      preview.getParent().remove(preview);
      preview = null;
    }

    first = null;
  }

  /**
   * Returns the cursor for the given event and handle.
   */
  protected Cursor getCursor(MouseEvent e, int index) {
    return null;
  }

  /**
   * Paints the visible handles of this handler.
   */
  public void paint(Graphics g) {
    if (handles != null && isHandlesVisible()) {
      for (int i = 0; i < handles.length; i++) {
        if (isHandleVisible(i) && g.hitClip(handles[i].x, handles[i].y, handles[i].width, handles[i].height)) {
          g.setColor(getHandleFillColor(i));
          g.fillRect(handles[i].x, handles[i].y, handles[i].width, handles[i].height);

          g.setColor(getHandleBorderColor(i));
          g.drawRect(handles[i].x, handles[i].y, handles[i].width - 1, handles[i].height - 1);
        }
      }
    }
  }

  /**
   * Returns the color used to draw the selection border. This implementation
   * returns null.
   */
  public Color getSelectionColor() {
    return null;
  }

  /**
   * Returns the stroke used to draw the selection border. This implementation
   * returns null.
   */
  public Stroke getSelectionStroke() {
    return null;
  }

  /**
   * Returns true if the handle at the specified index is enabled.
   */
  protected boolean isHandleEnabled(int index) {
    return true;
  }

  /**
   * Returns true if the handle at the specified index is visible.
   */
  protected boolean isHandleVisible(int index) {
    return !isLabel(index) || isLabelMovable();
  }

  /**
   * Returns the color to be used to fill the handle at the specified index.
   */
  protected Color getHandleFillColor(int index) {
    if (isLabel(index)) {
      return mxSwingConstants.LABEL_HANDLE_FILLCOLOR;
    }

    return mxSwingConstants.HANDLE_FILLCOLOR;
  }

  /**
   * Returns the border color of the handle at the specified index.
   */
  protected Color getHandleBorderColor(int index) {
    return mxSwingConstants.HANDLE_BORDERCOLOR;
  }

  /**
   * Invoked when the handler is no longer used. This is an empty
   * hook for subclassers.
   */
  protected void destroy() {
    // nop
  }

}
