// Copyright 2006-2015 Tobias Sargeant (tobias.sargeant@gmail.com).
//
// This file is part of the Carve CSG Library (http://carve-csg.com/)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#pragma once

#include <carve/carve.hpp>

#include <carve/math.hpp>
#include <carve/math_constants.hpp>

#include <carve/geom.hpp>

#include <vector>

#include <math.h>

#if defined(CARVE_DEBUG)
#include <iostream>
#endif

#if defined CARVE_USE_EXACT_PREDICATES
#include <carve/shewchuk_predicates.hpp>
#endif

namespace carve {
namespace geom2d {

typedef carve::geom::vector<2> P2;
typedef carve::geom::ray<2> Ray2;
typedef carve::geom::linesegment<2> LineSegment2;

struct p2_adapt_ident {
  P2& operator()(P2& p) const { return p; }
  const P2& operator()(const P2& p) const { return p; }
};

typedef std::vector<P2> P2Vector;

/**
 * \brief Return the orientation of c with respect to the ray defined by a->b.
 *
 * (Can be implemented exactly)
 *
 * @param[in] a
 * @param[in] b
 * @param[in] c
 *
 * @return positive, if c to the left of a->b.
 *         zero, if c is colinear with a->b.
 *         negative, if c to the right of a->b.
 */
#if defined CARVE_USE_EXACT_PREDICATES
inline double orient2d(const P2& a, const P2& b, const P2& c) {
  return shewchuk::orient2d(a.v, b.v, c.v);
}
#else
inline double orient2d(const P2& a, const P2& b, const P2& c) {
  double acx = a.x - c.x;
  double bcx = b.x - c.x;
  double acy = a.y - c.y;
  double bcy = b.y - c.y;
  return acx * bcy - acy * bcx;
}
#endif

/**
 * \brief Determine whether p is internal to the anticlockwise
 *        angle abc, where b is the apex of the angle.
 *
 * @param[in] a
 * @param[in] b
 * @param[in] c
 * @param[in] p
 *
 * @return true, if p is contained in the anticlockwise angle from
 *               b->a to b->c. Reflex angles contain p if p lies
 *               on b->a or on b->c. Acute angles do not contain p
 *               if p lies on b->a or on b->c. This is so that
 *               internalToAngle(a,b,c,p) = !internalToAngle(c,b,a,p)
 */
inline bool internalToAngle(const P2& a, const P2& b, const P2& c,
                            const P2& p) {
  bool reflex = (a < c) ? orient2d(b, a, c) <= 0.0 : orient2d(b, c, a) > 0.0;
  double d1 = orient2d(b, a, p);
  double d2 = orient2d(b, c, p);
  if (reflex) {
    return d1 >= 0.0 || d2 <= 0.0;
  } else {
    return d1 > 0.0 && d2 < 0.0;
  }
}

/**
 * \brief Determine whether p is internal to the anticlockwise
 *        angle ac, with apex at (0,0).
 *
 * @param[in] a
 * @param[in] c
 * @param[in] p
 *
 * @return true, if p is contained in a0c.
 */
inline bool internalToAngle(const P2& a, const P2& c, const P2& p) {
  return internalToAngle(a, P2::ZERO(), c, p);
}

template <typename P2vec>
bool isAnticlockwise(const P2vec& tri) {
  return orient2d(tri[0], tri[1], tri[2]) > 0.0;
}

template <typename P2vec>
bool pointIntersectsTriangle(const P2& p, const P2vec& tri) {
  int orient = isAnticlockwise(tri) ? +1 : -1;
  if (orient2d(tri[0], tri[1], p) * orient < 0) {
    return false;
  }
  if (orient2d(tri[1], tri[2], p) * orient < 0) {
    return false;
  }
  if (orient2d(tri[2], tri[0], p) * orient < 0) {
    return false;
  }
  return true;
}

template <typename P2vec>
bool lineIntersectsTriangle(const P2& p1, const P2& p2, const P2vec& tri) {
  double s[3];
  // does tri lie on one side or the other of p1-p2?
  s[0] = orient2d(p1, p2, tri[0]);
  s[1] = orient2d(p1, p2, tri[1]);
  s[2] = orient2d(p1, p2, tri[2]);
  if (*std::max_element(s, s + 3) < 0) {
    return false;
  }
  if (*std::min_element(s, s + 3) > 0) {
    return false;
  }

  // does line lie entirely to the right of a triangle edge?
  int orient = isAnticlockwise(tri) ? +1 : -1;
  if (orient2d(tri[0], tri[1], p1) * orient < 0 &&
      orient2d(tri[0], tri[1], p2) * orient < 0) {
    return false;
  }
  if (orient2d(tri[1], tri[2], p1) * orient < 0 &&
      orient2d(tri[1], tri[2], p2) * orient < 0) {
    return false;
  }
  if (orient2d(tri[2], tri[0], p1) * orient < 0 &&
      orient2d(tri[2], tri[0], p2) * orient < 0) {
    return false;
  }
  return true;
}

template <typename P2vec>
int triangleLineOrientation(const P2& p1, const P2& p2, const P2vec& tri) {
  double lo, hi, tmp;
  lo = hi = orient2d(p1, p2, tri[0]);
  tmp = orient2d(p1, p2, tri[1]);
  lo = std::min(lo, tmp);
  hi = std::max(hi, tmp);
  tmp = orient2d(p1, p2, tri[2]);
  lo = std::min(lo, tmp);
  hi = std::max(hi, tmp);
  if (hi < 0.0) {
    return -1;
  }
  if (lo > 0.0) {
    return +1;
  }
  return 0;
}

template <typename P2vec>
bool triangleIntersectsTriangle(const P2vec& tri_b, const P2vec& tri_a) {
  int orient_a = isAnticlockwise(tri_a) ? +1 : -1;
  if (triangleLineOrientation(tri_a[0], tri_a[1], tri_b) * orient_a < 0) {
    return false;
  }
  if (triangleLineOrientation(tri_a[1], tri_a[2], tri_b) * orient_a < 0) {
    return false;
  }
  if (triangleLineOrientation(tri_a[2], tri_a[0], tri_b) * orient_a < 0) {
    return false;
  }

  int orient_b = isAnticlockwise(tri_b) ? +1 : -1;
  if (triangleLineOrientation(tri_b[0], tri_b[1], tri_a) * orient_b < 0) {
    return false;
  }
  if (triangleLineOrientation(tri_b[1], tri_b[2], tri_a) * orient_b < 0) {
    return false;
  }
  if (triangleLineOrientation(tri_b[2], tri_b[0], tri_a) * orient_b < 0) {
    return false;
  }

  return true;
}

static inline double atan2(const P2& p) {
  return ::atan2(p.y, p.x);
}

struct LineIntersectionInfo {
  LineIntersectionClass iclass;
  P2 ipoint;
  int p1, p2;

  LineIntersectionInfo(LineIntersectionClass _iclass, P2 _ipoint = P2::ZERO(),
                       int _p1 = -1, int _p2 = -1)
      : iclass(_iclass), ipoint(_ipoint), p1(_p1), p2(_p2) {}
};

struct PolyInclusionInfo {
  PointClass iclass;
  int iobjnum;

  PolyInclusionInfo(PointClass _iclass, int _iobjnum = -1)
      : iclass(_iclass), iobjnum(_iobjnum) {}
};

struct PolyIntersectionInfo {
  IntersectionClass iclass;
  P2 ipoint;
  size_t iobjnum;

  PolyIntersectionInfo(IntersectionClass _iclass, const P2& _ipoint,
                       size_t _iobjnum)
      : iclass(_iclass), ipoint(_ipoint), iobjnum(_iobjnum) {}
};

bool lineSegmentIntersection_simple(const P2& l1v1, const P2& l1v2,
                                    const P2& l2v1, const P2& l2v2);
bool lineSegmentIntersection_simple(const LineSegment2& l1,
                                    const LineSegment2& l2);

LineIntersectionInfo lineSegmentIntersection(const P2& l1v1, const P2& l1v2,
                                             const P2& l2v1, const P2& l2v2);
LineIntersectionInfo lineSegmentIntersection(const LineSegment2& l1,
                                             const LineSegment2& l2);

int lineSegmentPolyIntersections(const std::vector<P2>& points,
                                 LineSegment2 line,
                                 std::vector<PolyInclusionInfo>& out);

int sortedLineSegmentPolyIntersections(const std::vector<P2>& points,
                                       LineSegment2 line,
                                       std::vector<PolyInclusionInfo>& out);

static inline bool quadIsConvex(const P2& a, const P2& b, const P2& c,
                                const P2& d) {
  double s_1, s_2;

  s_1 = carve::geom2d::orient2d(a, c, b);
  s_2 = carve::geom2d::orient2d(a, c, d);
  if ((s_1 < 0.0 && s_2 < 0.0) || (s_1 > 0.0 && s_2 > 0.0)) {
    return false;
  }

  s_1 = carve::geom2d::orient2d(b, d, a);
  s_2 = carve::geom2d::orient2d(b, d, c);
  if ((s_1 < 0.0 && s_2 < 0.0) || (s_1 > 0.0 && s_2 > 0.0)) {
    return false;
  }

  return true;
}

template <typename T, typename adapt_t>
inline bool quadIsConvex(const T& a, const T& b, const T& c, const T& d,
                         adapt_t adapt) {
  return quadIsConvex(adapt(a), adapt(b), adapt(c), adapt(d));
}

double signedArea(const std::vector<P2>& points);

static inline double signedArea(const P2& a, const P2& b, const P2& c) {
  return ((b.y + a.y) * (b.x - a.x) + (c.y + b.y) * (c.x - b.x) +
          (a.y + c.y) * (a.x - c.x)) /
         2.0;
}

template <typename T, typename adapt_t>
double signedArea(const std::vector<T>& points, adapt_t adapt) {
  P2Vector::size_type l = points.size();
  double A = 0.0;

  for (P2Vector::size_type i = 0; i < l - 1; i++) {
    A += (adapt(points[i + 1]).y + adapt(points[i]).y) *
         (adapt(points[i + 1]).x - adapt(points[i]).x);
  }
  A += (adapt(points[0]).y + adapt(points[l - 1]).y) *
       (adapt(points[0]).x - adapt(points[l - 1]).x);

  return A / 2.0;
}

template <typename iter_t, typename adapt_t>
double signedArea(iter_t begin, iter_t end, adapt_t adapt) {
  double A = 0.0;
  P2 p, n;

  if (begin == end) {
    return 0.0;
  }

  p = adapt(*begin);
  for (iter_t c = begin; ++c != end;) {
    P2 n = adapt(*c);
    A += (n.y + p.y) * (n.x - p.x);
    p = n;
  }
  n = adapt(*begin);
  A += (n.y + p.y) * (n.x - p.x);

  return A / 2.0;
}

bool pointInPolySimple(const std::vector<P2>& points, const P2& p);

template <typename T, typename adapt_t>
bool pointInPolySimple(const std::vector<T>& points, adapt_t adapt,
                       const P2& p) {
  CARVE_ASSERT(points.size() > 0);
  P2Vector::size_type l = points.size();
  double s = 0.0;
  double rp, r0, d;

  rp = r0 = atan2(adapt(points[0]) - p);

  for (P2Vector::size_type i = 1; i < l; i++) {
    double r = atan2(adapt(points[i]) - p);
    d = r - rp;
    if (d > M_PI) {
      d -= M_TWOPI;
    }
    if (d < -M_PI) {
      d += M_TWOPI;
    }
    s = s + d;
    rp = r;
  }

  d = r0 - rp;
  if (d > M_PI) {
    d -= M_TWOPI;
  }
  if (d < -M_PI) {
    d += M_TWOPI;
  }
  s = s + d;

  return !carve::math::ZERO(s);
}

PolyInclusionInfo pointInPoly(const std::vector<P2>& points, const P2& p);

template <typename T, typename adapt_t>
PolyInclusionInfo pointInPoly(const std::vector<T>& points, adapt_t adapt,
                              const P2& p) {
  P2Vector::size_type l = points.size();
  for (unsigned i = 0; i < l; i++) {
    if (equal(adapt(points[i]), p)) {
      return PolyInclusionInfo(POINT_VERTEX, (int)i);
    }
  }

  for (unsigned i = 0; i < l; i++) {
    unsigned j = (i + 1) % l;

    if (std::min(adapt(points[i]).x, adapt(points[j]).x) - CARVE_EPSILON < p.x &&
        std::max(adapt(points[i]).x, adapt(points[j]).x) + CARVE_EPSILON > p.x &&
        std::min(adapt(points[i]).y, adapt(points[j]).y) - CARVE_EPSILON < p.y &&
        std::max(adapt(points[i]).y, adapt(points[j]).y) + CARVE_EPSILON > p.y &&
        distance2(carve::geom::rayThrough(adapt(points[i]), adapt(points[j])),
                  p) < CARVE_EPSILON2) {
      return PolyInclusionInfo(POINT_EDGE, (int)i);
    }
  }

  if (pointInPolySimple(points, adapt, p)) {
    return PolyInclusionInfo(POINT_IN);
  }

  return PolyInclusionInfo(POINT_OUT);
}

bool pickContainedPoint(const std::vector<P2>& poly, P2& result);

template <typename T, typename adapt_t>
bool pickContainedPoint(const std::vector<T>& poly, adapt_t adapt, P2& result) {
#if defined(CARVE_DEBUG)
  std::cerr << "pickContainedPoint ";
  for (unsigned i = 0; i < poly.size(); ++i)
    std::cerr << " " << adapt(poly[i]);
  std::cerr << std::endl;
#endif

  const size_t S = poly.size();
  P2 a, b, c;
  for (unsigned i = 0; i < S; ++i) {
    a = adapt(poly[i]);
    b = adapt(poly[(i + 1) % S]);
    c = adapt(poly[(i + 2) % S]);

    if (cross(a - b, c - b) < 0) {
      P2 p = (a + b + c) / 3;
      if (pointInPolySimple(poly, adapt, p)) {
        result = p;
        return true;
      }
    }
  }
  return false;
}
}  // namespace geom2d
}  // namespace carve
