//
//  Copyright (C) 2021-2022 David Cosgrove and other RDKit contributors
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
// Original author: David Cosgrove (CozChemIx Limited)
//

#include <cmath>

#include <GraphMol/MolDraw2D/MolDraw2DDetails.h>
#include <GraphMol/MolDraw2D/DrawShape.h>
#include <GraphMol/MolDraw2D/DrawText.h>
#include <GraphMol/MolDraw2D/MolDraw2D.h>
#include <GraphMol/MolDraw2D/StringRect.h>

namespace RDKit {
namespace MolDraw2D_detail {

// ****************************************************************************
DrawShape::DrawShape(const std::vector<Point2D> &points, double lineWidth,
                     bool scaleLineWidth, DrawColour lineColour, bool fill,
                     int atom1, int atom2, int bond)
    : points_(points),
      lineWidth_(lineWidth),
      scaleLineWidth_(scaleLineWidth),
      lineColour_(lineColour),
      fill_(fill),
      atom1_(atom1),
      atom2_(atom2),
      bond_(bond) {}

// ****************************************************************************
void DrawShape::draw(MolDraw2D &drawer) {
  // the various myDraw functions may over-ride these.
  const auto ocolour = drawer.colour();
  drawer.setColour(lineColour_);
  const auto olw = drawer.lineWidth();
  drawer.setLineWidth(lineWidth_);
  const auto ofill = drawer.fillPolys();
  drawer.setFillPolys(fill_);
  const auto lineScale = drawer.drawOptions().scaleBondWidth;
  drawer.drawOptions().scaleBondWidth = scaleLineWidth_;
  drawer.setActiveAtmIdx(atom1_, atom2_);
  drawer.setActiveBndIdx(bond_);

  myDraw(drawer);

  drawer.setActiveAtmIdx();
  drawer.setActiveBndIdx();
  drawer.setColour(ocolour);
  drawer.setLineWidth(olw);
  drawer.setFillPolys(ofill);
  drawer.drawOptions().scaleBondWidth = lineScale;
}

// ****************************************************************************
void DrawShape::findExtremes(double &xmin, double &xmax, double &ymin,
                             double &ymax) const {
  for (const auto &p : points_) {
    xmin = std::min(xmin, p.x);
    xmax = std::max(xmax, p.x);
    ymin = std::min(ymin, p.y);
    ymax = std::max(ymax, p.y);
  }
}

// ****************************************************************************
void DrawShape::scale(const Point2D &scale_factor) {
  for (auto &p : points_) {
    p.x *= scale_factor.x;
    p.y *= scale_factor.y;
  }
}

// ****************************************************************************
void DrawShape::move(const Point2D &trans) {
  for (auto &p : points_) {
    p += trans;
  }
}

// ****************************************************************************
bool DrawShape::doesRectClash(const StringRect &, double) const {
  return false;
}

// ****************************************************************************
DrawShapeArrow::DrawShapeArrow(const std::vector<Point2D> &points,
                               double lineWidth, bool scaleLineWidth,
                               DrawColour lineColour, bool fill, int atom1,
                               int atom2, int bond, double frac, double angle)
    : DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1,
                atom2, bond),
      frac_(frac),
      angle_(angle) {
  PRECONDITION(points_.size() == 2, "arrow bad points size");
  // The two ends of the arrowhead are used for collision detection so we
  // may as well store them.  They won't be used for drawing.  lineWidth_ is
  // in pixels rather than drawing coords, so keep another set of coords that
  // are the original ends and arrowhead ends, with a lineWidth_ of 0, used
  // for findExtremes and doesRectClash.
  origPts_[0] = points_[0];
  origPts_[1] = points_[1];
  Point2D ab(points_[1]), p1, p2;
  MolDraw2D_detail::calcArrowHead(ab, p1, p2, points_[1], frac_, 0.0, angle_);
  origPts_[2] = p1;
  origPts_[3] = p2;
}

// ****************************************************************************
void DrawShapeArrow::myDraw(MolDraw2D &drawer) const {
  if (drawer.drawOptions().splitBonds) {
    drawer.setActiveAtmIdx(atom2_);
  } else {
    drawer.setActiveAtmIdx(atom1_, atom2_);
  }
  drawer.setActiveBndIdx(bond_);
  drawer.drawArrow(points_[0], points_[1], fill_, frac_, angle_, lineColour_,
                   true);
}

// ****************************************************************************
void DrawShapeArrow::findExtremes(double &xmin, double &xmax, double &ymin,
                                  double &ymax) const {
  // do it for origPts_ rather than points_.  The difference is that the
  // arrow ends haven't been adjusted for the lineWidth_.  It's pretty
  // inconceivable that this will matter for these purposes as the chances
  // of a dative bond going to an atom without a symbol on the edge of the
  // drawing seem slim.  findExtremes is only used for finding the extremes
  // of the whole drawing so as to set the scale.
  for (int i = 0; i < 4; i++) {
    const Point2D &p = origPts_[i];
    xmin = std::min(xmin, p.x);
    xmax = std::max(xmax, p.x);
    ymin = std::min(ymin, p.y);
    ymax = std::max(ymax, p.y);
  }
}

// ****************************************************************************
void DrawShapeArrow::scale(const Point2D &scale_factor) {
  DrawShape::scale(scale_factor);
  for (int i = 0; i < 4; i++) {
    Point2D &p = origPts_[i];
    p.x *= scale_factor.x;
    p.y *= scale_factor.y;
  }
}

// ****************************************************************************
void DrawShapeArrow::move(const Point2D &trans) {
  DrawShape::move(trans);
  for (int i = 0; i < 4; i++) {
    origPts_[i] += trans;
  }
}

// ****************************************************************************
bool DrawShapeArrow::doesRectClash(const StringRect &rect,
                                   double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  if (doesLineIntersect(rect, origPts_[0], origPts_[1], padding)) {
    return true;
  }
  if (doesLineIntersect(rect, origPts_[1], origPts_[2], padding)) {
    return true;
  }
  if (doesLineIntersect(rect, origPts_[1], origPts_[3], padding)) {
    return true;
  }
  if (doesLineIntersect(rect, origPts_[2], origPts_[3], padding)) {
    return true;
  }
  return false;
}

// ****************************************************************************
DrawShapeEllipse::DrawShapeEllipse(const std::vector<Point2D> &points,
                                   double lineWidth, bool scaleLineWidth,
                                   DrawColour lineColour, bool fill, int atom1)
    : DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1) {
  PRECONDITION(points_.size() == 2, "ellipse wrong points");
}

// ****************************************************************************
void DrawShapeEllipse::myDraw(MolDraw2D &drawer) const {
  if (fill_) {
    drawer.setLineWidth(1);
    drawer.drawOptions().scaleBondWidth = false;
  }
  auto p1 = points_[0] - points_[1];
  auto p2 = points_[0] + points_[1];
  drawer.drawEllipse(p1, p2, true);
}

// ****************************************************************************
void DrawShapeEllipse::findExtremes(double &xmin, double &xmax, double &ymin,
                                    double &ymax) const {
  // points_[0] is the centre, points_[1] the radii
  xmin = std::min(points_[0].x - points_[1].x, xmin);
  xmax = std::max(points_[0].x + points_[1].x, xmax);
  ymin = std::min(points_[0].y - points_[1].y, ymin);
  ymax = std::max(points_[0].y + points_[1].y, ymax);
}

// ****************************************************************************
void DrawShapeEllipse::move(const Point2D &trans) { points_[0] += trans; }
// ****************************************************************************
bool DrawShapeEllipse::doesRectClash(const StringRect &rect,
                                     double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  Point2D tl, tr, br, bl;
  rect.calcCorners(tl, tr, br, bl, padding);
  auto w = points_[1].x;
  auto h = points_[1].y;
  auto cx = points_[0].x;
  auto cy = points_[0].y;
  w = w > 0 ? w : -1 * w;
  h = h > 0 ? h : -1 * h;
  Point2D centre{cx, cy};
  if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, tl, tr)) {
    return true;
  }
  if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, tr, br)) {
    return true;
  }
  if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, br, bl)) {
    return true;
  }
  if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, bl, tl)) {
    return true;
  }
  return false;
}

// ****************************************************************************
DrawShapeSimpleLine::DrawShapeSimpleLine(const std::vector<Point2D> &points,
                                         double lineWidth, bool scaleLineWidth,
                                         DrawColour lineColour, int atom1,
                                         int atom2, int bond,
                                         DashPattern dashPattern)
    : DrawShape(points, lineWidth, scaleLineWidth, lineColour, false, atom1,
                atom2, bond),
      dashPattern_(dashPattern) {
  PRECONDITION(points_.size() == 2, "simple line wrong number of points");
}

// ****************************************************************************
void DrawShapeSimpleLine::myDraw(MolDraw2D &drawer) const {
  auto od = drawer.dash();
  auto dp = dashPattern_;
  if (dp == shortDashes) {
    // these are roughly equivalent to the original checks on scale,
    // which we don't have any more.
    double sq_len = (points_[0] - points_[1]).lengthSq();
    if (sq_len < 55.0) {
      dp[0] /= 4;
      dp[1] /= 3;
    } else if (sq_len < 900.0) {
      dp[0] /= 2;
      dp[1] /= 1.5;
    }
  }
  drawer.setDash(dp);
  drawer.setActiveAtmIdx(atom1_, atom2_);
  drawer.setActiveBndIdx(bond_);
  drawer.drawLine(points_[0], points_[1], lineColour_, lineColour_, true);
  drawer.setActiveAtmIdx();
  drawer.setActiveBndIdx();
  drawer.setDash(od);
}

// ****************************************************************************
bool DrawShapeSimpleLine::doesRectClash(const StringRect &rect,
                                        double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  return doesLineIntersect(rect, points_[0], points_[1], padding);
}

// ****************************************************************************
DrawShapePolyLine::DrawShapePolyLine(const std::vector<Point2D> &points,
                                     double lineWidth, bool scaleLineWidth,
                                     DrawColour lineColour, bool fill,
                                     int atom1, int atom2, int bond,
                                     DashPattern dashPattern)
    : DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1,
                atom2, bond),
      dashPattern_(dashPattern) {
  PRECONDITION(points_.size() > 2, "polyline not enough points");
}

// ****************************************************************************
void DrawShapePolyLine::myDraw(MolDraw2D &drawer) const {
  drawer.drawPolygon(points_, true);
}

// ****************************************************************************
bool DrawShapePolyLine::doesRectClash(const StringRect &rect,
                                      double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  Point2D tl, tr, br, bl;
  rect.calcCorners(tl, tr, br, bl, padding);
  for (size_t i = 1; i < points_.size(); ++i) {
    if (doesLineIntersect(rect, points_[i - 1], points_[i], padding)) {
      return true;
    }
  }
  return doesLineIntersect(rect, points_.front(), points_.back(), padding);
}

// ****************************************************************************
DrawShapeSolidWedge::DrawShapeSolidWedge(const std::vector<Point2D> points,
                                         const DrawColour &col1,
                                         const DrawColour &col2,
                                         bool splitBonds,
                                         std::vector<Point2D> &otherBondVecs,
                                         double lineWidth, int atom1, int atom2,
                                         int bond)
    : DrawShape(points, lineWidth / 2.0, false, col1, false, atom1, atom2,
                bond),
      col2_(col2),
      splitBonds_(splitBonds),
      otherBondVecs_(otherBondVecs) {
  PRECONDITION(points_.size() == 3, "solid wedge wrong points");
  if (otherBondVecs_.size() > 2) {
    trimOtherBondVecs();
  }
  if (otherBondVecs_.size() == 2) {
    orderOtherBondVecs();
  }
  buildTriangles();
}

// ****************************************************************************
void DrawShapeSolidWedge::buildTriangles() {
  if (!(lineColour_ == col2_) || splitBonds_) {
    buildTwoColorTriangles();
  } else {
    buildSingleColorTriangles();
  }
}

// ****************************************************************************
void DrawShapeSolidWedge::buildSingleColorTriangles() {
  auto point = points_[0];
  auto end1 = points_[1];
  auto end2 = points_[2];
  auto midEnd = (end1 + end2) / 2.0;
  auto adjend1 = end1;
  auto adjend2 = end2;
  points_.clear();
  // adjust adjend1 and adjend2 to line up with otherBondVecs_.
  if (otherBondVecs_.empty()) {
    points_.push_back(point);
    points_.push_back(adjend1);
    points_.push_back(adjend2);
  } else if (otherBondVecs_.size() == 1) {
    auto side1 = (end1 - point) * 2.0;
    if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend1)) {
      adjend1 = end1;
    }
    auto side2 = (end2 - point) * 2.0;
    if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend2)) {
      adjend2 = end2;
    }
    points_.push_back(point);
    points_.push_back(adjend1);
    points_.push_back(adjend2);
  } else if (otherBondVecs_.size() == 2) {
    auto side1 = (end1 - point) * 2.0;
    if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend1)) {
      adjend1 = end1;
    }
    points_.push_back(point);
    points_.push_back(adjend1);
    points_.push_back(midEnd);
    auto side2 = (end2 - point) * 2.0;
    if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[1],
                          midEnd + otherBondVecs_[1], &adjend2)) {
      adjend2 = end2;
    }
    points_.push_back(point);
    points_.push_back(midEnd);
    points_.push_back(adjend2);
  }
}

// ****************************************************************************
void DrawShapeSolidWedge::buildTwoColorTriangles() {
  auto point = points_[0];
  auto end1 = points_[1];
  auto end2 = points_[2];
  auto midEnd = (end1 + end2) / 2.0;
  auto adjend1 = end1;
  auto adjend2 = end2;
  points_.clear();
  auto e1 = end1 - point;
  auto e2 = end2 - point;
  auto mid1 = point + e1 * 0.5;
  auto mid2 = point + e2 * 0.5;
  points_.push_back(point);
  points_.push_back(mid1);
  points_.push_back(mid2);
  if (otherBondVecs_.empty()) {
    points_.push_back(mid1);
    points_.push_back(adjend2);
    points_.push_back(adjend1);
    points_.push_back(mid1);
    points_.push_back(mid2);
    points_.push_back(adjend2);
  } else if (otherBondVecs_.size() == 1) {
    auto side1 = (end1 - point) * 2.0;
    if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend1)) {
      adjend1 = end1;
    }
    auto side2 = (end2 - point) * 2.0;
    if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend2)) {
      adjend2 = end2;
    }
    points_.push_back(mid1);
    points_.push_back(adjend2);
    points_.push_back(adjend1);
    points_.push_back(mid1);
    points_.push_back(mid2);
    points_.push_back(adjend2);
  } else if (otherBondVecs_.size() == 2) {
    auto side1 = (end1 - point) * 2.0;
    if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
                          midEnd + otherBondVecs_[0], &adjend1)) {
      adjend1 = end1;
    }
    auto side2 = (end2 - point) * 2.0;
    if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[1],
                          midEnd + otherBondVecs_[1], &adjend2)) {
      adjend2 = end2;
    }
    points_.push_back(mid1);
    points_.push_back(adjend1);
    points_.push_back(midEnd);
    points_.push_back(midEnd);
    points_.push_back(mid2);
    points_.push_back(mid1);
    points_.push_back(midEnd);
    points_.push_back(adjend2);
    points_.push_back(mid2);
  }
}

// ****************************************************************************
void DrawShapeSolidWedge::myDraw(MolDraw2D &drawer) const {
  drawer.setFillPolys(true);
  if (drawer.drawOptions().splitBonds) {
    drawer.setActiveAtmIdx(atom1_);
  } else {
    drawer.setActiveAtmIdx(atom1_, atom2_);
  }
  drawer.setActiveBndIdx(bond_);
  if (points_.size() == 3 || points_.size() == 9) {
    drawer.drawTriangle(points_[0], points_[1], points_[2], true);
  }
  if (points_.size() == 6) {
    // This is a 2 triangle drawing where the wedge goes onto a Y shape, as in
    // Github 7739 tests in catch_tests.cpp.
    std::vector<Point2D> quadPoints{points_[0], points_[1], points_[2],
                                    points_[5]};
    drawer.drawPolygon(quadPoints, true);
  } else if (points_.size() == 9) {
    // This is a conventional 2-colour wedge originally drawn as 3 triangles.
    if (drawer.drawOptions().splitBonds) {
      drawer.setActiveAtmIdx(atom2_);
    }
    drawer.setColour(col2_);
    std::vector<Point2D> quadPoints{points_[4], points_[5], points_[6],
                                    points_[7]};
    drawer.drawPolygon(quadPoints, true);
  }
}

// ****************************************************************************
bool DrawShapeSolidWedge::doesRectClash(const StringRect &rect,
                                        double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  if (doesTriangleIntersect(rect, points_[0], points_[1], points_[2],
                            padding)) {
    return true;
  }
  if (points_.size() >= 6) {
    if (doesTriangleIntersect(rect, points_[3], points_[4], points_[5],
                              padding)) {
      return true;
    }
    if (points_.size() >= 9) {
      if (doesTriangleIntersect(rect, points_[6], points_[7], points_[8],
                                padding)) {
        return true;
      }
    }
  }
  return false;
}

// ****************************************************************************
void DrawShapeSolidWedge::trimOtherBondVecs() {
  if (otherBondVecs_.size() < 3) {
    return;
  }
  int firstVec = 0, secondVec = 1;
  double largestAng = -361.0;
  for (unsigned int i = 0; i < otherBondVecs_.size() - 1; ++i) {
    for (unsigned int j = i + 1; j < otherBondVecs_.size(); ++j) {
      auto ang = otherBondVecs_[i].angleTo(otherBondVecs_[j]);
      if (ang > largestAng) {
        firstVec = i;
        secondVec = j;
        largestAng = ang;
      }
    }
  }
  std::vector<Point2D> newVecs{otherBondVecs_[firstVec],
                               otherBondVecs_[secondVec]};
  otherBondVecs_ = newVecs;
}

// ****************************************************************************
void DrawShapeSolidWedge::orderOtherBondVecs() {
  if (otherBondVecs_.size() < 2) {
    return;
  }
  // otherBondVecs_[0] needs to be on the same side as points_[1], which
  // implies the larger dot product between it and the vector along the
  // triangle edge.
  auto side1 = (points_[0] - points_[1]);
  auto mid = (points_[1] + points_[2]) / 2.0;
  auto midp1 = mid.directionVector(points_[1]);
  auto dot1 = midp1.dotProduct(otherBondVecs_[0]);
  auto dot2 = midp1.dotProduct(otherBondVecs_[1]);
  if (dot1 < dot2) {
    std::swap(otherBondVecs_[0], otherBondVecs_[1]);
  }
}

// ****************************************************************************
DrawShapeDashedWedge::DrawShapeDashedWedge(const std::vector<Point2D> points,
                                           const DrawColour &col1,
                                           const DrawColour &col2,
                                           bool oneLessDash, double lineWidth,
                                           int atom1, int atom2, int bond)
    : DrawShape(points, lineWidth, false, col1, false, atom1, atom2, bond),
      col2_(col2),
      oneLessDash_(oneLessDash) {
  PRECONDITION(points_.size() == 3, "dashed wedge wrong points");
  at1Cds_ = points[0];
  end1Cds_ = points[1];
  end2Cds_ = points[2];
  buildLines();
}

// ****************************************************************************
void DrawShapeDashedWedge::buildLines() {
  auto midend = (end1Cds_ + end2Cds_) * 0.5;
  points_.clear();
  lineColours_.clear();
  auto e1 = at1Cds_.directionVector(end1Cds_);
  auto e2 = at1Cds_.directionVector(end2Cds_);
  // the ACS1996 rules say the dash separation should be 2.5px.  It seems
  // like a good result for all of them.
  // It appears that this means a 2.5px gap between each line, so we need
  // to take the line width into account.  Each line that the gap is
  // between will contribute half a width.
  double dashSep = 2.5 + lineWidth_;
  double centralLen = (at1Cds_ - midend).length();
  unsigned int nDashes = rdcast<unsigned int>(std::round(centralLen / dashSep));
  // There should be at least 3 dashes so we can see which way the wedge
  // is going (Github6041b).
  unsigned int numDashesNeeded = oneLessDash_ ? 4 : 3;
  if (nDashes < numDashesNeeded) {
    nDashes = numDashesNeeded;
  }
  if (!nDashes) {
    points_.push_back(end1Cds_);
    points_.push_back(end2Cds_);
    lineColours_.push_back(lineColour_);
  } else {
    // re-adjust so the last dash is on the end of the wedge.
    dashSep = centralLen / rdcast<double>(nDashes);
    // if doing one less dash, we want a shorter wedge that is just as wide
    // at the end as it would have been.
    if (oneLessDash_) {
      double endlenb2 = (end1Cds_ - end2Cds_).length() / 2.0;
      auto centralLine = at1Cds_.directionVector(midend);
      Point2D centralPerp{-centralLine.y, centralLine.x};
      Point2D newEnd1 = at1Cds_ + centralLine * (centralLen - dashSep) +
                        centralPerp * endlenb2;
      Point2D newEnd2 = at1Cds_ + centralLine * (centralLen - dashSep) -
                        centralPerp * endlenb2;
      e1 = at1Cds_.directionVector(newEnd1);
      e2 = at1Cds_.directionVector(newEnd2);
    }
    // we want the separation down the sides of the triangle, so use
    // similar triangles to scale.
    dashSep *= (end1Cds_ - at1Cds_).length() / centralLen;
    int extra = oneLessDash_ ? 0 : 1;
    for (unsigned int i = 1; i < nDashes + extra; ++i) {
      auto e11 = at1Cds_ + e1 * rdcast<double>(i) * dashSep;
      auto e22 = at1Cds_ + e2 * rdcast<double>(i) * dashSep;
      points_.push_back(e11);
      points_.push_back(e22);
      if (i > nDashes / 2) {
        lineColours_.push_back(col2_);
      } else {
        lineColours_.push_back(lineColour_);
      }
    }
  }
}

// ****************************************************************************
void DrawShapeDashedWedge::myDraw(MolDraw2D &drawer) const {
  drawer.setFillPolys(false);
  drawer.setActiveAtmIdx(atom1_, atom2_);
  drawer.setActiveBndIdx(bond_);
  for (size_t i = 0, j = 0; i < points_.size(); i += 2, ++j) {
    if (drawer.drawOptions().splitBonds) {
      if (i < points_.size() / 2) {
        drawer.setActiveAtmIdx(atom1_);
      } else {
        drawer.setActiveAtmIdx(atom2_);
      }
    }
    drawer.drawLine(points_[i], points_[i + 1], lineColours_[j],
                    lineColours_[j], true);
  }
}

// ****************************************************************************
void DrawShapeDashedWedge::scale(const Point2D &scale_factor) {
  DrawShape::scale(scale_factor);
  at1Cds_.x *= scale_factor.x;
  at1Cds_.y *= scale_factor.y;
  end1Cds_.x *= scale_factor.x;
  end1Cds_.y *= scale_factor.y;
  end2Cds_.x *= scale_factor.x;
  end2Cds_.y *= scale_factor.y;
  buildLines();
}

// ****************************************************************************
void DrawShapeDashedWedge::move(const Point2D &trans) {
  DrawShape::move(trans);
  at1Cds_ += trans;
  end1Cds_ += trans;
  end2Cds_ += trans;
}

// ****************************************************************************
void DrawShapeDashedWedge::findExtremes(double &xmin, double &xmax,
                                        double &ymin, double &ymax) const {
  xmin = std::min({at1Cds_.x, end1Cds_.x, end2Cds_.x, xmin});
  xmax = std::max({at1Cds_.x, end1Cds_.x, end2Cds_.x, xmax});
  ymin = std::min({at1Cds_.y, end1Cds_.y, end2Cds_.y, ymin});
  ymax = std::max({at1Cds_.y, end1Cds_.y, end2Cds_.y, ymax});
}

// ****************************************************************************
bool DrawShapeDashedWedge::doesRectClash(const StringRect &rect,
                                         double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  return doesTriangleIntersect(rect, at1Cds_, end1Cds_, end2Cds_, padding);
}

// ****************************************************************************
DrawShapeWavyLine::DrawShapeWavyLine(const std::vector<Point2D> points,
                                     double lineWidth, bool scaleLineWidth,
                                     const DrawColour &col1,
                                     const DrawColour &col2, double offset,
                                     int atom1, int atom2, int bond)
    : DrawShape(points, lineWidth, scaleLineWidth, col1, false, atom1, atom2,
                bond),
      col2_(col2),
      offset_(offset) {
  PRECONDITION(points_.size() == 2, "wavy line wrong points");
}

// ****************************************************************************
void DrawShapeWavyLine::myDraw(MolDraw2D &drawer) const {
  // nSegments is 16 by default in MolDraw2D.
  // use a negative offset because of inverted y coords to make it look the
  // same as it used to.
  int nsegs = int(
      std::round((points_[0] - points_[1]).length() / (offset_ * 2.0 / 3.0)));
  drawer.drawWavyLine(points_[0], points_[1], lineColour_, col2_, nsegs,
                      -offset_ / 2.0, true);
}

// ****************************************************************************
void DrawShapeWavyLine::scale(const Point2D &scaleFactor) {
  DrawShape::scale(scaleFactor);
  offset_ *= scaleFactor.x;
}

// ****************************************************************************
bool DrawShapeWavyLine::doesRectClash(const StringRect &rect,
                                      double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  padding += offset_;
  return doesLineIntersect(rect, points_[0], points_[1], padding);
}

// ****************************************************************************
DrawShapeArc::DrawShapeArc(const std::vector<Point2D> points, double ang1,
                           double ang2, double lineWidth, bool scaleLineWidth,
                           const DrawColour &col1, bool fill, int atom1)
    : DrawShape(points, lineWidth, scaleLineWidth, col1, fill, atom1),
      ang1_(ang1),
      ang2_(ang2) {
  PRECONDITION(points_.size() == 2, "arc wrong points");
  RANGE_CHECK(0., ang1_, 360.);
  RANGE_CHECK(0., ang2_, 360.);
}

// ****************************************************************************
void DrawShapeArc::myDraw(MolDraw2D &drawer) const {
  if (fill_) {
    drawer.setLineWidth(1);
    drawer.drawOptions().scaleBondWidth = false;
  }
  double start_ang = ang1_ > ang2_ ? ang1_ - 360.0 : ang1_;
  drawer.drawArc(points_[0], points_[1].x, points_[1].y, start_ang, ang2_,
                 true);
}

// ****************************************************************************
void DrawShapeArc::findExtremes(double &xmin, double &xmax, double &ymin,
                                double &ymax) const {
  xmin = std::min(xmin, points_[0].x - points_[1].x);
  xmax = std::max(xmax, points_[0].x + points_[1].x);
  ymin = std::min(ymin, points_[0].y - points_[1].y);
  ymax = std::max(ymax, points_[0].y + points_[1].y);
}

// ****************************************************************************
void DrawShapeArc::move(const Point2D &trans) { points_[0] += trans; }

// ****************************************************************************
bool DrawShapeArc::doesRectClash(const StringRect &rect, double padding) const {
  padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
  Point2D tl, tr, br, bl;
  rect.calcCorners(tl, tr, br, bl, padding);
  if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
                           padding, tl, tr)) {
    return true;
  }
  if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
                           padding, tr, br)) {
    return true;
  }
  if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
                           padding, br, bl)) {
    return true;
  }
  if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
                           padding, bl, tl)) {
    return true;
  }
  return false;
}

}  // namespace MolDraw2D_detail
}  // namespace RDKit
