/**
 * $Id: mxConnectorShape.java,v 1.2 2013/08/28 06:32:23 gaudenz Exp $
 * Copyright (c) 2010, Gaudenz Alder, David Benson
 */
package com.mxgraph.shape;

import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxLine;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class mxConnectorShape extends mxBasicShape {

  /**
   *
   */
  public void paintShape(mxGraphics2DCanvas canvas, mxCellState state) {
    if (state.getAbsolutePointCount() > 1 && configureGraphics(canvas, state, false)) {
      List<mxPoint> pts = new ArrayList<mxPoint>(state.getAbsolutePoints());
      Map<String, Object> style = state.getStyle();

      // Paints the markers and updates the points
      // Switch off any dash pattern for markers
      boolean dashed = mxUtils.isTrue(style, mxConstants.STYLE_DASHED);
      Object dashedValue = style.get(mxConstants.STYLE_DASHED);

      if (dashed) {
        style.remove(mxConstants.STYLE_DASHED);
        canvas.getGraphics().setStroke(canvas.createStroke(style));
      }

      translatePoint(pts, 0, paintMarker(canvas, state, true));
      translatePoint(pts, pts.size() - 1, paintMarker(canvas, state, false));

      if (dashed) {
        // Replace the dash pattern
        style.put(mxConstants.STYLE_DASHED, dashedValue);
        canvas.getGraphics().setStroke(canvas.createStroke(style));
      }

      paintPolyline(canvas, pts, state.getStyle());
    }
  }

  /**
   *
   */
  protected void paintPolyline(mxGraphics2DCanvas canvas, List<mxPoint> points, Map<String, Object> style) {
    boolean rounded = isRounded(style) && canvas.getScale() > mxConstants.MIN_SCALE_FOR_ROUNDED_LINES;

    canvas.paintPolyline(points.toArray(new mxPoint[points.size()]), rounded);
  }

  /**
   *
   */
  public boolean isRounded(Map<String, Object> style) {
    return mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false);
  }

  /**
   *
   */
  private void translatePoint(List<mxPoint> points, int index, mxPoint offset) {
    if (offset != null) {
      mxPoint pt = (mxPoint)points.get(index).clone();
      pt.setX(pt.getX() + offset.getX());
      pt.setY(pt.getY() + offset.getY());
      points.set(index, pt);
    }
  }

  /**
   * Draws the marker for the given edge.
   *
   * @return the offset of the marker from the end of the line
   */
  public mxPoint paintMarker(mxGraphics2DCanvas canvas, mxCellState state, boolean source) {
    Map<String, Object> style = state.getStyle();
    float strokeWidth = (float)(mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * canvas.getScale());
    String type = mxUtils.getString(style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, "");
    float size =
      (mxUtils.getFloat(style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE));
    Color color = mxUtils.getColor(style, mxConstants.STYLE_STROKECOLOR);
    canvas.getGraphics().setColor(color);

    double absSize = size * canvas.getScale();

    List<mxPoint> points = state.getAbsolutePoints();
    mxLine markerVector = getMarkerVector(points, source, absSize);
    mxPoint p0 = new mxPoint(markerVector.getX(), markerVector.getY());
    mxPoint pe = markerVector.getEndPoint();

    mxPoint offset = null;

    // Computes the norm and the inverse norm
    double dx = pe.getX() - p0.getX();
    double dy = pe.getY() - p0.getY();

    double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
    double unitX = dx / dist;
    double unitY = dy / dist;
    double nx = unitX * absSize;
    double ny = unitY * absSize;

    // Allow for stroke width in the end point used and the
    // orthogonal vectors describing the direction of the
    // marker
    double strokeX = unitX * strokeWidth;
    double strokeY = unitY * strokeWidth;
    pe = (mxPoint)pe.clone();
    pe.setX(pe.getX() - strokeX / 2.0);
    pe.setY(pe.getY() - strokeY / 2.0);

    mxIMarker marker = mxMarkerRegistry.getMarker(type);

    if (marker != null) {
      offset = marker.paintMarker(canvas, state, type, pe, nx, ny, absSize, source);

      if (offset != null) {
        offset.setX(offset.getX() - strokeX / 2.0);
        offset.setY(offset.getY() - strokeY / 2.0);
      }
    }
    else {
      // Offset for the strokewidth
      nx = dx * strokeWidth / dist;
      ny = dy * strokeWidth / dist;

      offset = new mxPoint(-strokeX / 2.0, -strokeY / 2.0);
    }

    return offset;
  }

  /**
   * Hook to override creation of the vector that the marker is drawn along
   * since it may not be the same as the vector between any two control
   * points
   *
   * @param points     the guide points of the connector
   * @param source     whether the marker is at the source end
   * @param markerSize the scaled maximum length of the marker
   * @return a line describing the vector the marker should be drawn along
   */
  protected mxLine getMarkerVector(List<mxPoint> points, boolean source, double markerSize) {
    int n = points.size();
    mxPoint p0 = (source) ? points.get(1) : points.get(n - 2);
    mxPoint pe = (source) ? points.get(0) : points.get(n - 1);
    int count = 1;

    // Uses next non-overlapping point
    while (count < n - 1 && Math.round(p0.getX() - pe.getX()) == 0 && Math.round(p0.getY() - pe.getY()) == 0) {
      p0 = (source) ? points.get(1 + count) : points.get(n - 2 - count);
      count++;
    }

    return new mxLine(p0, pe);
  }

}
