/**
   * {@inheritDoc}
   *
   * @see VertexView#getPerimeterPoint(EdgeView, Point2D, Point2D)
   */
  @Override
  public final Point2D getPerimeterPoint(
      EdgeView edge, @SuppressWarnings("unused") Point2D source, Point2D p) {
    Rectangle2D r = getBounds();

    State state = null;
    if (getCell() instanceof DefaultStateView) {
      DefaultStateView defaultStateView = (DefaultStateView) getCell();
      state = defaultStateView.getState();
    }

    boolean loopTransition = false;
    State otherState = null;
    if (edge != null) {
      loopTransition = edge.getSource().getParentView() == edge.getTarget().getParentView();

      if (edge.getSource().getParentView() == this) {
        StateView stateView = (StateView) edge.getTarget().getParentView();
        if (stateView.getCell() instanceof DefaultStateView) {
          DefaultStateView defaultStateView = (DefaultStateView) stateView.getCell();
          otherState = defaultStateView.getState();
        }
      } else {
        StateView stateView = (StateView) edge.getSource().getParentView();
        if (stateView.getCell() instanceof DefaultStateView) {
          DefaultStateView defaultStateView = (DefaultStateView) stateView.getCell();
          otherState = defaultStateView.getState();
        }
      }
    }

    double x = r.getX();
    double y = r.getY();
    double a = r.getWidth() / 2;
    double b = r.getHeight() / 2;

    // x1, y1 - point
    double x1 = p.getX();
    double y1 = p.getY();

    if ((state != null) && (otherState != null) && !loopTransition) {
      boolean firstTransition = false;
      for (Transition current : state.getTransitionBegin()) {
        if (current.getStateEnd() == otherState) {
          firstTransition = true;
        }
      }

      boolean secondTransition = false;
      for (Transition current : otherState.getTransitionBegin()) {
        if (current.getStateEnd() == state) {
          secondTransition = true;
        }
      }

      if (otherState.isStartState() && !(firstTransition && secondTransition)) {
        x1 += START_OFFSET / 2;
      }
      if (otherState.isLoopTransition() && !(firstTransition && secondTransition)) {
        y1 += LOOP_TRANSITION_OFFSET / 2;
      }
    }

    if (state != null) {
      if (state.isStartState()) {
        x = r.getX() + START_OFFSET;
        a = (r.getWidth() - START_OFFSET) / 2;
      }
      if (state.isLoopTransition()) {
        y = r.getY() + LOOP_TRANSITION_OFFSET;
        b = (r.getHeight() - LOOP_TRANSITION_OFFSET) / 2;
      }
    }

    // x0, y0 - center of ellipse
    double x0 = x + a;
    double y0 = y + b;

    // calculate straight line equation through point and ellipse center
    double dx = x1 - x0;
    double dy = y1 - y0;

    if (dx == 0) {
      return new Point((int) x0, (int) (y0 + b * dy / Math.abs(dy)));
    }

    double d = dy / dx;
    double h = y0 - d * x0;

    // calculate intersection
    double e = a * a * d * d + b * b;
    double f = -2 * x0 * e;
    double g = a * a * d * d * x0 * x0 + b * b * x0 * x0 - a * a * b * b;

    double det = Math.sqrt(f * f - 4 * e * g);

    // two solutions (perimeter points)
    double xout1 = (-f + det) / (2 * e);
    double xout2 = (-f - det) / (2 * e);
    double yout1 = d * xout1 + h;
    double yout2 = d * xout2 + h;

    double dist1Squared = Math.pow((xout1 - x1), 2) + Math.pow((yout1 - y1), 2);
    double dist2Squared = Math.pow((xout2 - x1), 2) + Math.pow((yout2 - y1), 2);

    // correct solution
    double xout, yout;

    if (dist1Squared < dist2Squared) {
      xout = xout1;
      yout = yout1;
    } else {
      xout = xout2;
      yout = yout2;
    }

    if ((state != null) && state.isPowerState()) {
      int checkX = (int) (xout - x);
      int checkY = (int) (yout - y);

      if ((checkX >= a) && (checkY <= b)) {
        Color color = getColor(POWER_STATE_100, checkX, checkY);
        while ((color.getRed() == 0) && (color.getGreen() == 0) && (color.getBlue() == 0)) {
          xout = xout + 1;
          yout = yout - 1;
          checkX = (int) (xout - x);
          checkY = (int) (yout - y);

          color = getColor(POWER_STATE_100, checkX, checkY);
        }
      } else if ((checkX <= a) && (checkY <= b)) {
        Color color = getColor(POWER_STATE_100, checkX, checkY);
        while ((color.getRed() == 0) && (color.getGreen() == 0) && (color.getBlue() == 0)) {
          xout = xout - 1;
          yout = yout - 1;
          checkX = (int) (xout - x);
          checkY = (int) (yout - y);

          color = getColor(POWER_STATE_100, checkX, checkY);
        }
      } else if ((checkX <= a) && (checkY >= b)) {
        Color color = getColor(POWER_STATE_100, checkX, checkY);
        while ((color.getRed() == 0) && (color.getGreen() == 0) && (color.getBlue() == 0)) {
          xout = xout - 1;
          yout = yout + 1;
          checkX = (int) (xout - x);
          checkY = (int) (yout - y);

          color = getColor(POWER_STATE_100, checkX, checkY);
        }
      } else if ((checkX >= a) && (checkY >= b)) {
        Color color = getColor(POWER_STATE_100, checkX, checkY);
        while ((color.getRed() == 0) && (color.getGreen() == 0) && (color.getBlue() == 0)) {
          xout = xout + 1;
          yout = yout + 1;
          checkX = (int) (xout - x);
          checkY = (int) (yout - y);

          color = getColor(POWER_STATE_100, checkX, checkY);
        }
      }
    }

    return getAttributes().createPoint(xout, yout);
  }
    /**
     * {@inheritDoc}
     *
     * @see VertexRenderer#paint(Graphics)
     */
    @Override
    public final void paint(Graphics g) {
      State state = null;
      DefaultStateView defaultStateView = null;
      if (this.stateView.getCell() instanceof DefaultStateView) {
        defaultStateView = (DefaultStateView) this.stateView.getCell();
        state = defaultStateView.getState();
      } else {
        return;
      }
      int b = this.borderWidth;

      int offsetX = state.isStartState() ? START_OFFSET : 0;
      int offsetY = state.isLoopTransition() ? LOOP_TRANSITION_OFFSET : 0;

      Graphics2D g2 = (Graphics2D) g;
      Dimension d = getSize();
      boolean tmp = this.selected;
      if (super.isOpaque()) {
        Color background = null;
        // overwritten color
        if (defaultStateView.getOverwrittenColor() != null) {
          background = defaultStateView.getOverwrittenColor();
        }
        // error
        else if (state.isError()) {
          background = this.preferenceStateError;
        }
        // active
        else if (state.isActive()) {
          background = this.preferenceStateActive;
        }
        // start
        else if (state.isStartState()) {
          background = this.preferenceStateStart;
        }
        // final
        else if (state.isFinalState()) {
          background = this.preferenceStateFinal;
        }
        // normal
        else {
          background = this.preferenceStateBackground;
        }

        g.setColor(background);

        if ((this.gradientColor != null) && !this.preview) {
          setOpaque(false);
          g2.setPaint(
              new GradientPaint(
                  0, 0, background, getWidth(), getHeight(), this.gradientColor, true));
        }

        // final state
        if (state.isPowerState()) {
          if (state.isFinalState()) {
            g.fillRoundRect(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY,
                50,
                50);
          } else {
            g.fillRoundRect(
                offsetX + b - 1,
                offsetY + b - 1,
                d.width - b - offsetX,
                d.height - b - offsetY,
                50,
                50);
          }
        } else {
          if (state.isFinalState()) {
            g.fillOval(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY);
          } else {
            g.fillOval(
                offsetX + b - 1, offsetY + b - 1, d.width - b - offsetX, d.height - b - offsetY);
          }
        }
      }
      try {
        setBorder(null);
        setOpaque(false);
        this.selected = false;

        g.setColor(this.preferenceState);
        g.setFont(getFont());
        FontMetrics metrics = g.getFontMetrics();

        int dx =
            offsetX + ((d.width - offsetX) / 2) - (metrics.stringWidth(state.toString()) / 2) - 1;
        int dy = offsetY + ((d.height - offsetY) / 2) + (metrics.getHeight() / 2) - 3;

        PrettyString prettyString = new PrettyString();
        prettyString.add(state.toPrettyString());

        int insets = state.isFinalState() ? 20 : 10;
        // short version
        if ((metrics.stringWidth(state.toString()) + insets) > (d.width - offsetX)) {
          state.setShortNameUsed(true);

          dx = insets / 2;
          String dots = " ..."; // $NON-NLS-1$
          PrettyToken lastPrettyToken = null;
          while ((!prettyString.isEmpty())
              && ((metrics.stringWidth(prettyString.toString() + dots) + 2 * dx)
                  > (d.width - offsetX))) {
            lastPrettyToken = prettyString.removeLastPrettyToken();
          }

          if (lastPrettyToken != null) {
            char[] chars = lastPrettyToken.getChar();
            int i = 0;
            String addText = ""; // $NON-NLS-1$
            while ((i < chars.length)
                && ((metrics.stringWidth(prettyString.toString() + addText + dots) + 2 * dx)
                    <= (d.width - offsetX))) {
              addText += chars[i];
              i++;
            }

            if (addText.length() > 0) {
              addText = addText.substring(0, addText.length() - 1);
            }

            prettyString.add(new PrettyToken(addText, lastPrettyToken.getStyle()));
          }

          dx += offsetX;

          // center the dots if there are no pretty tokens
          if (prettyString.isEmpty()) {
            dots = "..."; // $NON-NLS-1$
            dx = offsetX + ((d.width - offsetX) / 2) - (metrics.stringWidth(dots) / 2) - 1;
          }

          prettyString.add(new PrettyToken(dots, Style.NONE));
        }
        // normal version
        else {
          state.setShortNameUsed(false);
        }

        for (PrettyToken currentToken : prettyString) {
          Font font = null;

          if (!currentToken.isBold() && !currentToken.isItalic()) {
            font = g.getFont().deriveFont(Font.PLAIN);
          } else if (currentToken.isBold() && currentToken.isItalic()) {
            font = g.getFont().deriveFont(Font.BOLD | Font.ITALIC);
          } else if (currentToken.isBold()) {
            font = g.getFont().deriveFont(Font.BOLD);
          } else if (currentToken.isItalic()) {
            font = g.getFont().deriveFont(Font.ITALIC);
          }

          g.setFont(font);
          g.setColor(currentToken.getColor());

          char[] chars = currentToken.getChar();
          for (int i = 0; i < chars.length; i++) {
            g.drawChars(chars, i, 1, dx, dy);
            dx += metrics.charWidth(chars[i]);
          }
        }
      } finally {
        this.selected = tmp;
      }
      if (this.bordercolor != null) {
        g.setColor(this.bordercolor);
        g2.setStroke(new BasicStroke(b));

        if (state.isPowerState()) {
          g.drawRoundRect(
              offsetX + b - 1,
              offsetY + b - 1,
              d.width - b - offsetX,
              d.height - b - offsetY,
              50,
              50);
          if (state.isFinalState()) {
            g.drawRoundRect(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY,
                50,
                50);
          }
        } else {
          g.drawOval(
              offsetX + b - 1, offsetY + b - 1, d.width - b - offsetX, d.height - b - offsetY);
          if (state.isFinalState()) {
            g.drawOval(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY);
          }
        }
      }
      if (this.selected) {
        g.setColor(this.preferenceStateSelected);

        if (state.isPowerState()) {
          g.drawRoundRect(
              offsetX + b - 1,
              offsetY + b - 1,
              d.width - b - offsetX,
              d.height - b - offsetY,
              50,
              50);
          if (state.isFinalState()) {
            g.drawRoundRect(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY,
                50,
                50);
          }
        } else {
          g.drawOval(
              offsetX + b - 1, offsetY + b - 1, d.width - b - offsetX, d.height - b - offsetY);
          if (state.isFinalState()) {
            g.drawOval(
                offsetX + b + 3,
                offsetY + b + 3,
                d.width - b - 8 - offsetX,
                d.height - b - 8 - offsetY);
          }
        }
      }
      if (state.isStartState()) {
        g.setFont(getFont().deriveFont(Font.BOLD));
        g.setColor(this.preferenceTransition);

        g.drawLine(0, offsetY + 35, START_OFFSET, offsetY + 35);

        g.drawLine(START_OFFSET - 2, offsetY + 34, START_OFFSET - 2, offsetY + 36);
        g.drawLine(START_OFFSET - 3, offsetY + 33, START_OFFSET - 3, offsetY + 37);
        g.drawLine(START_OFFSET - 4, offsetY + 32, START_OFFSET - 4, offsetY + 38);
        g.drawLine(START_OFFSET - 5, offsetY + 31, START_OFFSET - 5, offsetY + 39);
        g.drawLine(START_OFFSET - 6, offsetY + 30, START_OFFSET - 6, offsetY + 40);

        g.drawString("Start", 10, offsetY + 30); // $NON-NLS-1$
      }
    }