/*
 * openTCS copyright information:
 * Copyright (c) 2005-2011 ifak e.V.
 * Copyright (c) 2012 Fraunhofer IML
 *
 * This program is free software and subject to the MIT license. (For details,
 * see the licensing information (LICENSE.txt) you should have received with
 * this copy of the software.)
 */
package org.opentcs.guing.components.drawing.figures;

import com.google.inject.assistedinject.Assisted;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.Collection;
import java.util.EventObject;
import java.util.LinkedList;
import java.util.List;
import static java.util.Objects.requireNonNull;
import java.util.StringJoiner;
import javax.inject.Inject;
import org.jhotdraw.draw.AttributeKey;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.DrawingView;
import org.jhotdraw.draw.connector.ChopEllipseConnector;
import org.jhotdraw.draw.connector.Connector;
import org.jhotdraw.draw.handle.BezierOutlineHandle;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.liner.ElbowLiner;
import org.jhotdraw.draw.liner.Liner;
import org.jhotdraw.draw.liner.SlantedLiner;
import org.jhotdraw.geom.BezierPath;
import org.opentcs.guing.components.drawing.course.Origin;
import org.opentcs.guing.components.drawing.figures.liner.BezierLinerControlPointHandle;
import org.opentcs.guing.components.drawing.figures.liner.PolyPathLiner;
import org.opentcs.guing.components.drawing.figures.liner.TripleBezierLiner;
import org.opentcs.guing.components.drawing.figures.liner.TupelBezierLiner;
import org.opentcs.guing.components.properties.event.AttributesChangeEvent;
import org.opentcs.guing.components.properties.type.AbstractProperty;
import org.opentcs.guing.components.properties.type.LengthProperty;
import org.opentcs.guing.components.properties.type.SpeedProperty;
import org.opentcs.guing.components.properties.type.StringProperty;
import org.opentcs.guing.model.ModelComponent;
import org.opentcs.guing.model.elements.PathModel;
import org.opentcs.guing.model.elements.PointModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A connection between two points.
 *
 * @author Heinz Huber (Fraunhofer IML)
 * @author Stefan Walter (Fraunhofer IML)
 */
public class PathConnection
    extends SimpleLineConnection {

  /**
   * This class's logger.
   */
  private static final Logger LOG = LoggerFactory.getLogger(PathConnection.class);
  /**
   * The dash pattern for locked paths.
   */
  private static final double[] LOCKED_DASH = {6.0, 4.0};
  /**
   * The dash pattern for unlocked paths.
   */
  private static final double[] UNLOCKED_DASH = {10.0, 0.0};
  /**
   * The tool tip text generator.
   */
  private final ToolTipTextGenerator textGenerator;
  /**
   * Control point 1.
   */
  private Point2D.Double cp1;
  /**
   * Control point 2.
   */
  private Point2D.Double cp2;
  /**
   * Control point 3.
   */
  private Point2D.Double cp3;
  /**
   * Control point 4.
   */
  private Point2D.Double cp4;
  /**
   * Control point 5.
   */
  private Point2D.Double cp5;

  private Origin previousOrigin;

  /**
   * Creates a new instance.
   *
   * @param model The model corresponding to this graphical object.
   * @param textGenerator The tool tip text generator.
   */
  @Inject
  public PathConnection(@Assisted PathModel model,
                        ToolTipTextGenerator textGenerator) {
    super(model);
    this.textGenerator = requireNonNull(textGenerator, "textGenerator");
    resetPath();
  }

  @Override
  public PathModel getModel() {
    return (PathModel) get(FigureConstants.MODEL);
  }

  @Override
  public void updateConnection() {
    super.updateConnection();
    initializePreviousOrigin();
    updateControlPoints();
  }

  /**
   * Resets control points and connects start and end point with a straight line.
   */
  private void resetPath() {
    Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
    Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);

    path.clear();
    path.add(new BezierPath.Node(sp));
    path.add(new BezierPath.Node(ep));
    cp1 = null;
    cp2 = null;
    cp3 = null;
    cp4 = null;
    cp5 = null;
    getModel().getPropertyPathControlPoints().markChanged();
  }

  /**
   * Bei Umwandlung von DIRECT/ELBOW/SLANTED in BEZIER-Kurve:
   * Initiale Kontrollpunkte bei 1/n, 2/n, ... der Strecke setzen.
   *
   * @param type the type of the curve
   */
  private void initControlPoints(PathModel.Type type) {
    Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
    Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);

    if (sp.x != ep.x || sp.y != ep.y) {
      path.clear();
      if (type == PathModel.Type.BEZIER_3) { //BEZIER curve with 3 control points);
        //Add the scaled vector between start and endpoint to the startpoint
        cp1 = new Point2D.Double(sp.x + (ep.x - sp.x) * 1 / 6, sp.y + (ep.y - sp.y) * 1 / 6); //point at 1/6
        cp2 = new Point2D.Double(sp.x + (ep.x - sp.x) * 2 / 6, sp.y + (ep.y - sp.y) * 2 / 6); //point at 2/6
        cp3 = new Point2D.Double(sp.x + (ep.x - sp.x) * 3 / 6, sp.y + (ep.y - sp.y) * 3 / 6); //point at 3/6
        cp4 = new Point2D.Double(sp.x + (ep.x - sp.x) * 4 / 6, sp.y + (ep.y - sp.y) * 4 / 6); //point at 4/6
        cp5 = new Point2D.Double(sp.x + (ep.x - sp.x) * 5 / 6, sp.y + (ep.y - sp.y) * 5 / 6); //point at 5/6
        path.add(new BezierPath.Node(BezierPath.C2_MASK,
                                     sp.x, sp.y, //Current point
                                     sp.x, sp.y, //Previous point - not in use because of C2_MASK
                                     cp1.x, cp1.y)); //Next point
        //Use cp1 and cp2 to draw between sp and cp3
        path.add(new BezierPath.Node(BezierPath.C1C2_MASK,
                                     cp3.x, cp3.y, //Current point
                                     cp2.x, cp2.y, //Previous point
                                     cp4.x, cp4.y)); //Next point
        //Use cp4 and cp5 to draw between cp3 and ep
        path.add(new BezierPath.Node(BezierPath.C1_MASK,
                                     ep.x, ep.y, //Current point
                                     cp5.x, cp5.y, //Previous point
                                     ep.x, ep.y)); //Next point - not in use because of C1_MASK
      }
      else {
        cp1 = new Point2D.Double(sp.x + (ep.x - sp.x) / 3, sp.y + (ep.y - sp.y) / 3); //point at 1/3
        cp2 = new Point2D.Double(ep.x - (ep.x - sp.x) / 3, ep.y - (ep.y - sp.y) / 3); //point at 2/3
        cp3 = null;
        cp4 = null;
        cp5 = null;
        path.add(new BezierPath.Node(BezierPath.C2_MASK,
                                     sp.x, sp.y, //Current point
                                     sp.x, sp.y, //Previous point - not in use because of C2_MASK
                                     cp1.x, cp1.y)); //Next point
        path.add(new BezierPath.Node(BezierPath.C1_MASK,
                                     ep.x, ep.y, //Current point
                                     cp2.x, cp2.y, //Previous point
                                     ep.x, ep.y)); //Next point - not in use because of C1_MASK
      }

      getModel().getPropertyPathControlPoints().markChanged();
      path.invalidatePath();
    }
  }

  /**
   * Im "alten" Modell wird zu quadratischen Kurven ein Kontrollpunkt
   * gespeichert, zu kubischen Kurven zwei.
   *
   * @param cp1
   * @param cp2 Identisch mit cp1 bei quadratischen Kurven
   */
  public void addControlPoints(Point2D.Double cp1, Point2D.Double cp2) {
    this.cp1 = cp1;
    this.cp2 = cp2;
    this.cp3 = null;
    Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
    Point2D.Double ep = path.get(1, BezierPath.C0_MASK);
    path.clear();
    path.add(new BezierPath.Node(BezierPath.C2_MASK,
                                 sp.x, sp.y, //Current point
                                 sp.x, sp.y, //Previous point
                                 cp1.x, cp1.y)); //Next point
    path.add(new BezierPath.Node(BezierPath.C1_MASK,
                                 ep.x, ep.y, //Current point
                                 cp2.x, cp2.y, //Previous point
                                 ep.x, ep.y)); //Next point
    //getModel().getProperty(ElementPropKeys.PATH_CONTROL_POINTS).markChanged();
  }

  /**
   * A bezier curve with three control points.
   *
   * @param cp1 Control point 1
   * @param cp2 Control point 2
   * @param cp3 Control point 3
   * @param cp4 Control point 4
   * @param cp5 Control point 5
   */
  public void addControlPoints(Point2D.Double cp1,
                               Point2D.Double cp2,
                               Point2D.Double cp3,
                               Point2D.Double cp4,
                               Point2D.Double cp5) {
    this.cp1 = cp1;
    this.cp2 = cp2;
    this.cp3 = cp3;
    this.cp4 = cp4;
    this.cp5 = cp5;
    Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
    Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);
    path.clear();
    path.add(new BezierPath.Node(BezierPath.C2_MASK,
                                 sp.x, sp.y, //Current point
                                 sp.x, sp.y, //Previous point
                                 cp1.x, cp1.y)); //Next point
    //Use cp1 and cp2 to draw between sp and cp3
    path.add(new BezierPath.Node(BezierPath.C1C2_MASK,
                                 cp3.x, cp3.y, //Current point
                                 cp2.x, cp2.y, //Previous point
                                 cp4.x, cp4.y)); //Next point
    //Use cp4 and cp5 to draw between cp3 and ep
    path.add(new BezierPath.Node(BezierPath.C1_MASK,
                                 ep.x, ep.y, //Current point
                                 cp5.x, cp5.y, //Previous point
                                 cp4.x, cp4.y)); //Next point
    StringProperty sProp = getModel().getPropertyPathControlPoints();
    sProp.setText(String.format("%d,%d;%d,%d;%d,%d;%d,%d;%d,%d;",
                                (int) cp1.x, (int) cp1.y,
                                (int) cp2.x, (int) cp2.y,
                                (int) cp3.x, (int) cp3.y,
                                (int) cp4.x, (int) cp4.y,
                                (int) cp5.x, (int) cp5.y));
    sProp.markChanged();
    getModel().propertiesChanged(this);
  }

  public Point2D.Double getCp1() {
    return cp1;
  }

  public Point2D.Double getCp2() {
    return cp2;
  }

  public Point2D.Double getCp3() {
    return cp3;
  }

  public Point2D.Double getCp4() {
    return cp4;
  }

  public Point2D.Double getCp5() {
    return cp5;
  }

  @Override
  public Point2D.Double getCenter() {
    // Computes the center of the curve.
    // Approximation: Center of the control points.
    Point2D.Double p1;
    Point2D.Double p2;
    Point2D.Double pc;

    p1 = (cp1 == null ? path.get(0, BezierPath.C0_MASK) : cp1);
    p2 = (cp2 == null ? path.get(1, BezierPath.C0_MASK) : cp2);
    if (cp3 == null) {
      pc = new Point2D.Double((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
    }
    else {
      //Use cp3 for 3-bezier as center because the curve goes through it at 50%
      pc = (cp3 == null ? path.get(3, BezierPath.C0_MASK) : cp3);
    }

    return pc;
  }

  /**
   * Initializes the previous origin which is used to scale the control points of this path.
   */
  private void initializePreviousOrigin() {
    if (previousOrigin == null) {
      Origin origin = get(FigureConstants.ORIGIN);
      previousOrigin = new Origin();
      previousOrigin.setScale(origin.getScaleX(), origin.getScaleY());
    }
  }

  /**
   * Die Bezier und Polypath Kontrollpunkte aktualisieren
   */
  public void updateControlPoints() {
    String sControlPoints = "";
    if (getLinerType() == PathModel.Type.POLYPATH) {
      sControlPoints = updatePolyPathControlPoints();
    }
    else if (getLinerType() == PathModel.Type.BEZIER || getLinerType() == PathModel.Type.BEZIER_3) {
      sControlPoints = updateBezierControlPoints();
    }

    StringProperty sProp = getModel().getPropertyPathControlPoints();
    sProp.setText(sControlPoints);
    invalidate();
    sProp.markChanged();
    getModel().propertiesChanged(this);
  }

  private String updatePolyPathControlPoints() {
    StringJoiner rtn = new StringJoiner(";");
    for (int i = 1; i < path.size() - 1; i++) {
      Point2D.Double p = path.get(i, BezierPath.C0_MASK);
      rtn.add(String.format("%d,%d", (int) p.x, (int) p.y));
    }
    return rtn.toString();
  }

  private String updateBezierControlPoints() {
    if (cp1 != null && cp2 != null) {
      if (cp3 != null) {
        cp1 = path.get(0, BezierPath.C2_MASK);
        cp2 = path.get(1, BezierPath.C1_MASK);
        cp3 = path.get(1, BezierPath.C0_MASK);
        cp4 = path.get(1, BezierPath.C2_MASK);
        cp5 = path.get(2, BezierPath.C1_MASK);
      }
      else {
        cp1 = path.get(0, BezierPath.C2_MASK);
        cp2 = path.get(1, BezierPath.C1_MASK);
      }
    }
    String sControlPoints = "";
    if (cp1 != null) {
      if (cp2 != null) {
        if (cp3 != null) {
          // Format: x1,y1;x2,y2;x3,y3;x4,y4;x5,y5
          sControlPoints = String.format("%d,%d;%d,%d;%d,%d;%d,%d;%d,%d",
                                         (int) (cp1.x),
                                         (int) (cp1.y),
                                         (int) (cp2.x),
                                         (int) (cp2.y),
                                         (int) (cp3.x),
                                         (int) (cp3.y),
                                         (int) (cp4.x),
                                         (int) (cp4.y),
                                         (int) (cp5.x),
                                         (int) (cp5.y));
        }
        else {
          // Format: x1,y1;x2,y2
          sControlPoints = String.format("%d,%d;%d,%d", (int) (cp1.x),
                                         (int) (cp1.y), (int) (cp2.x),
                                         (int) (cp2.y));
        }
      }
      else {
        // Format: x1,y1
        sControlPoints = String.format("%d,%d", (int) (cp1.x), (int) (cp1.y));
      }
    }
    return sControlPoints;
  }

  /**
   * Connects two figures with this connection.
   *
   * @param start The first figure.
   * @param end The second figure.
   */
  public void connect(LabeledPointFigure start, LabeledPointFigure end) {
    // ChopEllipseConnector zeichnet die Linienenden/Pfeilspitzen auf
    // die direkte Verbindungsgerade von Start nach End.
    // TODO: Auf Tangenten zu Controlpoints ausrichten?
    Connector compConnector = new ChopEllipseConnector();
    Connector startConnector = start.findCompatibleConnector(compConnector, true);
    Connector endConnector = end.findCompatibleConnector(compConnector, true);

    if (!canConnect(startConnector, endConnector)) {
      return;
    }

    setStartConnector(startConnector);
    setEndConnector(endConnector);

    getModel().setConnectedComponents(start.get(FigureConstants.MODEL),
                                      end.get(FigureConstants.MODEL));
  }

  /**
   * Returns the type of this path.
   *
   * @return The type of this path
   */
  public PathModel.Type getLinerType() {
    return (PathModel.Type) getModel().getPropertyPathConnType().getValue();
  }

  public void setLinerByType(PathModel.Type type) {
    switch (type) {
      case DIRECT:
        resetPath();
        updateLiner(null);
        break;

      case ELBOW:
        if (!(getLiner() instanceof ElbowLiner)) {
          resetPath();
          updateLiner(new ElbowLiner());
        }

        break;

      case SLANTED:
        if (!(getLiner() instanceof SlantedLiner)) {
          resetPath();
          updateLiner(new SlantedLiner());
        }

        break;

      case BEZIER:
        if (!(getLiner() instanceof TupelBezierLiner)) {
          initControlPoints(type);
          updateLiner(new TupelBezierLiner());
        }
        break;
      case BEZIER_3:
        if (!(getLiner() instanceof TripleBezierLiner)) {
          initControlPoints(type);
          updateLiner(new TripleBezierLiner());
        }
        break;
      case POLYPATH:
        if (!(getLiner() instanceof PolyPathLiner)) {
          initPolyPath();
          updateLiner(new PolyPathLiner());
        }
        break;
      default:
        setLiner(null);
    }
  }

  private void updateLiner(Liner newLiner) {
    setLiner(newLiner);
    fireFigureHandlesChanged();
    fireAreaInvalidated();
    updateControlPoints();
    invalidate();
    getModel().propertiesChanged(this);
  }

  private LengthProperty calculateLength() {
    try {
      LengthProperty property = getModel().getPropertyLength();

      if (property != null) {
        double length = (double) property.getValue();
        // Tbd: Wann soll die L�nge aus dem Abstand der verbundenen Punkte neu berechnet werden?
        if (length <= 0.0) {
          PointFigure start = ((LabeledPointFigure) getStartFigure()).getPresentationFigure();
          PointFigure end = ((LabeledPointFigure) getEndFigure()).getPresentationFigure();
          double startPosX = start.getModel().getPropertyModelPositionX().getValueByUnit(LengthProperty.Unit.MM);
          double startPosY = start.getModel().getPropertyModelPositionY().getValueByUnit(LengthProperty.Unit.MM);
          double endPosX = end.getModel().getPropertyModelPositionX().getValueByUnit(LengthProperty.Unit.MM);
          double endPosY = end.getModel().getPropertyModelPositionY().getValueByUnit(LengthProperty.Unit.MM);
          length = distance(startPosX, startPosY, endPosX, endPosY);
          property.setValueAndUnit(length, LengthProperty.Unit.MM);
          property.markChanged();
        }
      }

      return property;
    }
    catch (IllegalArgumentException ex) {
      LOG.error("calculateLength()", ex);
      return null;
    }
  }

  @Override
  public String getToolTipText(Point2D.Double p) {
    return textGenerator.getToolTipText(getModel());
  }

  /**
   * Checks whether two points can be connected with each other.
   *
   * @param start The start connector.
   * @param end The end connector.
   * @return {@code true}, if the two points can be connected, otherwise {@code false}.
   */
  @Override
  public boolean canConnect(Connector start, Connector end) {
    ModelComponent modelStart = start.getOwner().get(FigureConstants.MODEL);
    ModelComponent modelEnd = end.getOwner().get(FigureConstants.MODEL);

    return modelStart instanceof PointModel
        && modelEnd instanceof PointModel
        && modelStart != modelEnd;
  }

  @Override
  public Collection<Handle> createHandles(int detailLevel) {
    List<Handle> handles = new LinkedList<>();

    // see BezierFigure
    switch (detailLevel % 2) {
      case -1: // Mouse hover handles
        handles.add(new BezierOutlineHandle(this, true));
        break;

      case 0:  // Mouse clicked
        if (getLinerType() == PathModel.Type.POLYPATH) {
          for (int i = 1; i < path.size() - 1; i++) {
            handles.add(new BezierLinerControlPointHandle(this, i, BezierPath.C0_MASK));
          }
        }
        else {
          if (cp1 != null) {
            // Startpunkt: Handle nach CP2
            handles.add(new BezierLinerControlPointHandle(this, 0, BezierPath.C2_MASK));
            if (cp2 != null) {
              // Endpunkt: Handle für CP3
              handles.add(new BezierLinerControlPointHandle(this, 1, BezierPath.C1_MASK));
              if (cp3 != null) {
                // Endpunkt: Handle nach EP
                handles.add(new BezierLinerControlPointHandle(this, 2, BezierPath.C1_MASK));
              }
            }
          }
        }

        break;

      case 1:  // double click
        // Rechteckiger Rahmen + Drehpunkt
//      TransformHandleKit.addTransformHandles(this, handles);
        handles.add(new BezierOutlineHandle(this));
        break;

      default:
    }

    return handles;
  }

  @Override
  public void lineout() {
    if (getLiner() == null) {
      path.invalidatePath();
    }
    else {
      getLiner().lineout(this);
    }
  }

  @Override
  public void propertiesChanged(AttributesChangeEvent e) {
    if (!e.getInitiator().equals(this)) {
      setLinerByType((PathModel.Type) getModel().getPropertyPathConnType().getValue());
      calculateLength();
      lineout();
    }

    super.propertiesChanged(e);
  }

  @Override
  public void updateDecorations() {
    if (getModel() == null) {
      return;
    }

    set(AttributeKeys.START_DECORATION, navigableBackward() ? ARROW_BACKWARD : null);
    set(AttributeKeys.END_DECORATION, navigableForward() ? ARROW_FORWARD : null);

    // Mark locked path.
    if (Boolean.TRUE.equals(getModel().getPropertyLocked().getValue())) {
      set(AttributeKeys.STROKE_COLOR, Color.red);
      set(AttributeKeys.STROKE_DASHES, LOCKED_DASH);
    }
    else {
      set(AttributeKeys.STROKE_COLOR, Color.black);
      set(AttributeKeys.STROKE_DASHES, UNLOCKED_DASH);
    }
  }

  @Override
  public <T> void set(AttributeKey<T> key, T newValue) {
    super.set(key, newValue);
    // if the ModelComponent is set we update the decorations, because
    // properties like maxReverseVelocity could have changed
    if (key.equals(FigureConstants.MODEL)) {
      updateDecorations();
    }
  }

  @Override
  public void updateModel() {
    if (calculateLength() == null) {
      return;
    }

    getModel().getPropertyMaxVelocity().markChanged();
    getModel().getPropertyMaxReverseVelocity().markChanged();

    getModel().propertiesChanged(this);
  }

  @Override
  public void scaleModel(EventObject event) {
    if (!(event.getSource() instanceof Origin)) {
      return;
    }

    Origin origin = (Origin) event.getSource();
    if (previousOrigin.getScaleX() == origin.getScaleX()
        && previousOrigin.getScaleY() == origin.getScaleY()) {
      return;
    }

    if (isTupelBezier()) { // BEZIER
      Point2D.Double scaledControlPoint = scaleControlPoint(cp1, origin);
      path.set(0, BezierPath.C2_MASK, scaledControlPoint);
      scaledControlPoint = scaleControlPoint(cp2, origin);
      path.set(1, BezierPath.C1_MASK, scaledControlPoint);
    }
    else if (isTripleBezier()) { // BEZIER_3
      Point2D.Double scaledControlPoint = scaleControlPoint(cp1, origin);
      path.set(0, BezierPath.C2_MASK, scaledControlPoint);
      scaledControlPoint = scaleControlPoint(cp2, origin);
      path.set(1, BezierPath.C1_MASK, scaledControlPoint);
      scaledControlPoint = scaleControlPoint(cp3, origin);
      path.set(1, BezierPath.C0_MASK, scaledControlPoint);
      scaledControlPoint = scaleControlPoint(cp4, origin);
      path.set(1, BezierPath.C2_MASK, scaledControlPoint);
      path.set(2, BezierPath.C2_MASK, scaledControlPoint);
      scaledControlPoint = scaleControlPoint(cp5, origin);
      path.set(2, BezierPath.C1_MASK, scaledControlPoint);
    }

    // Remember the new scale
    previousOrigin.setScale(origin.getScaleX(), origin.getScaleY());
    updateControlPoints();
  }

  private boolean navigableForward() {
    return getModel().getPropertyMaxVelocity().getValueByUnit(SpeedProperty.Unit.MM_S) > 0.0;
  }

  private boolean navigableBackward() {
    return getModel().getPropertyMaxReverseVelocity().getValueByUnit(SpeedProperty.Unit.MM_S) > 0.0;
  }

  private Point2D.Double scaleControlPoint(Point2D.Double p, Origin newScale) {
    return new Double((p.x * previousOrigin.getScaleX()) / newScale.getScaleX(),
                      (p.y * previousOrigin.getScaleY()) / newScale.getScaleY());
  }

  private boolean isTupelBezier() {
    return cp1 != null && cp2 != null && cp3 == null && cp4 == null && cp5 == null;
  }

  private boolean isTripleBezier() {
    return cp1 != null && cp2 != null && cp3 != null && cp4 != null && cp5 != null;
  }

  @Override // LineConnectionFigure
  public PathConnection clone() {
    PathConnection clone = (PathConnection) super.clone();

    AbstractProperty pConnType = (AbstractProperty) clone.getModel().getPropertyPathConnType();
    if (getLiner() instanceof TupelBezierLiner) {
      pConnType.setValue(PathModel.Type.BEZIER);
    }
    else if (getLiner() instanceof TripleBezierLiner) {
      pConnType.setValue(PathModel.Type.BEZIER_3);
    }
    else if (getLiner() instanceof ElbowLiner) {
      pConnType.setValue(PathModel.Type.ELBOW);
    }
    else if (getLiner() instanceof SlantedLiner) {
      pConnType.setValue(PathModel.Type.SLANTED);
    }

    return clone;
  }

  private void initPolyPath() {
    Point2D.Double sp = path.get(0, BezierPath.C0_MASK);
    Point2D.Double ep = path.get(path.size() - 1, BezierPath.C0_MASK);

    if (sp.x == ep.x && sp.y == ep.y) {
      path.clear();
      path.add(sp);
      String[] coords = getModel().getPropertyPathControlPoints().getText().split("[;]");
      for (String coordinates : coords) {
        String[] c = coordinates.split("[,]");
        int x = (int) java.lang.Double.parseDouble(c[0]);
        int y = (int) java.lang.Double.parseDouble(c[1]);
        path.add(x, y);
      }

      path.add(ep);
    }
    else {

      path.clear();
      path.add(new BezierPath.Node(sp));
      path.add(new BezierPath.Node((sp.x + ep.x) / 2.0, (sp.y + ep.y) / 2.0));
      path.add(new BezierPath.Node(ep));
    }
  }

  @Override // SimpleLineConnection
  public boolean handleMouseClick(Point2D.Double p, MouseEvent evt, DrawingView drawingView) {
    if (getLinerType() == PathModel.Type.POLYPATH) {
      int addPointMask = MouseEvent.CTRL_DOWN_MASK;
      int deletePointMask = MouseEvent.ALT_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK;
      if ((evt.getModifiersEx() & (addPointMask | deletePointMask)) == addPointMask) {
        int index = path.findSegment(p, 10);
        if (index != -1) {
          path.add(index + 1, new BezierPath.Node(p));
          updateConnection();
        }
      }
      else if ((evt.getModifiersEx() & (deletePointMask | addPointMask)) == deletePointMask) {
        int index = path.findSegment(p, 10);
        if (index != -1) {
          Point2D.Double[] points = path.toPolygonArray();
          path.clear();
          for (int i = 0; i < points.length; i++) {
            if (i != index) {
              path.add(new BezierPath.Node(points[i]));
            }
          }
          updateConnection();
        }
      }
    }
    return false;
  }
}