// 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.

#if defined(HAVE_CONFIG_H)
#include <carve_config.h>
#endif

#include <carve/aabb.hpp>
#include <carve/geom2d.hpp>
#include <carve/math.hpp>

#include <algorithm>
#include <iostream>

namespace carve {
namespace geom2d {

bool lineSegmentIntersection_simple(const P2& l1v1, const P2& l1v2,
                                    const P2& l2v1, const P2& l2v2) {
  geom::aabb<2> l1_aabb, l2_aabb;
  l1_aabb.fit(l1v1, l1v2);
  l2_aabb.fit(l2v1, l2v2);

  if (l1_aabb.maxAxisSeparation(l2_aabb) > 0.0) {
    return false;
  }

  double l1v1_side = orient2d(l2v1, l2v2, l1v1);
  double l1v2_side = orient2d(l2v1, l2v2, l1v2);

  double l2v1_side = orient2d(l1v1, l1v2, l2v1);
  double l2v2_side = orient2d(l1v1, l1v2, l2v2);

  if (l1v1_side * l1v2_side > 0.0 || l2v1_side * l2v2_side > 0.0) {
    return false;
  }

  return true;
}

bool lineSegmentIntersection_simple(const LineSegment2& l1,
                                    const LineSegment2& l2) {
  return lineSegmentIntersection_simple(l1.v1, l1.v2, l2.v1, l2.v2);
}

LineIntersectionInfo lineSegmentIntersection(const P2& l1v1, const P2& l1v2,
                                             const P2& l2v1, const P2& l2v2) {
  geom::aabb<2> l1_aabb, l2_aabb;
  l1_aabb.fit(l1v1, l1v2);
  l2_aabb.fit(l2v1, l2v2);

  if (l1_aabb.maxAxisSeparation(l2_aabb) > CARVE_EPSILON) {
    return LineIntersectionInfo(NO_INTERSECTION);
  }

  if (carve::geom::equal(l1v1, l1v2) || carve::geom::equal(l2v1, l2v2)) {
    throw carve::exception("zero length line in intersection test");
  }

  double dx13 = l1v1.x - l2v1.x;
  double dy13 = l1v1.y - l2v1.y;
  double dx43 = l2v2.x - l2v1.x;
  double dy43 = l2v2.y - l2v1.y;
  double dx21 = l1v2.x - l1v1.x;
  double dy21 = l1v2.y - l1v1.y;
  double ua_n = dx43 * dy13 - dy43 * dx13;
  double ub_n = dx21 * dy13 - dy21 * dx13;
  double u_d = dy43 * dx21 - dx43 * dy21;

  if (carve::math::ZERO(u_d)) {
    if (carve::math::ZERO(ua_n)) {
      if (carve::geom::equal(l1v2, l2v1)) {
        return LineIntersectionInfo(INTERSECTION_PP, l1v2, 1, 2);
      }
      if (carve::geom::equal(l1v1, l2v2)) {
        return LineIntersectionInfo(INTERSECTION_PP, l1v1, 0, 4);
      }
      if (l1v2.x > l2v1.x && l1v1.x < l2v2.x) {
        return LineIntersectionInfo(COLINEAR);
      }
    }
    return LineIntersectionInfo(NO_INTERSECTION);
  }

  double ua = ua_n / u_d;
  double ub = ub_n / u_d;

  if (-CARVE_EPSILON <= ua && ua <= 1.0 + CARVE_EPSILON && -CARVE_EPSILON <= ub &&
      ub <= 1.0 + CARVE_EPSILON) {
    double x = l1v1.x + ua * (l1v2.x - l1v1.x);
    double y = l1v1.y + ua * (l1v2.y - l1v1.y);

    P2 p = carve::geom::VECTOR(x, y);

    double d1 = distance2(p, l1v1);
    double d2 = distance2(p, l1v2);
    double d3 = distance2(p, l2v1);
    double d4 = distance2(p, l2v2);

    int n = -1;

    if (std::min(d1, d2) < CARVE_EPSILON2) {
      if (d1 < d2) {
        p = l1v1;
        n = 0;
      } else {
        p = l1v2;
        n = 1;
      }
      if (std::min(d3, d4) < CARVE_EPSILON2) {
        if (d3 < d4) {
          return LineIntersectionInfo(INTERSECTION_PP, p, n, 2);
        } else {
          return LineIntersectionInfo(INTERSECTION_PP, p, n, 3);
        }
      } else {
        return LineIntersectionInfo(INTERSECTION_PL, p, n, -1);
      }
    } else if (std::min(d3, d4) < CARVE_EPSILON2) {
      if (d3 < d4) {
        return LineIntersectionInfo(INTERSECTION_LP, l2v1, -1, 2);
      } else {
        return LineIntersectionInfo(INTERSECTION_LP, l2v2, -1, 3);
      }
    } else {
      return LineIntersectionInfo(INTERSECTION_LL, p, -1, -1);
    }
  }
  return LineIntersectionInfo(NO_INTERSECTION);
}

LineIntersectionInfo lineSegmentIntersection(const LineSegment2& l1,
                                             const LineSegment2& l2) {
  return lineSegmentIntersection(l1.v1, l1.v2, l2.v1, l2.v2);
}

double signedArea(const P2Vector& points) {
  return signedArea(points, p2_adapt_ident());
}

bool pointInPolySimple(const P2Vector& points, const P2& p) {
  return pointInPolySimple(points, p2_adapt_ident(), p);
}

PolyInclusionInfo pointInPoly(const P2Vector& points, const P2& p) {
  return pointInPoly(points, p2_adapt_ident(), p);
}

int lineSegmentPolyIntersections(const P2Vector& points, LineSegment2 line,
                                 std::vector<PolyIntersectionInfo>& out) {
  int count = 0;

  if (line.v2 < line.v1) {
    line.flip();
  }
  out.clear();

  for (P2Vector::size_type i = 0, l = points.size(); i < l; i++) {
    P2Vector::size_type j = (i + 1) % l;
    LineIntersectionInfo e =
        lineSegmentIntersection(LineSegment2(points[i], points[j]), line);

    switch (e.iclass) {
      case INTERSECTION_PL: {
        out.push_back(PolyIntersectionInfo(INTERSECT_EDGE, e.ipoint, i));
        count++;
        break;
      }
      case INTERSECTION_PP: {
        out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, e.ipoint,
                                           i + (size_t)e.p2 - 2));
        count++;
        break;
      }
      case INTERSECTION_LP: {
        out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, e.ipoint,
                                           i + (size_t)e.p2 - 2));
        count++;
        break;
      }
      case INTERSECTION_LL: {
        out.push_back(PolyIntersectionInfo(INTERSECT_EDGE, e.ipoint, i));
        count++;
        break;
      }
      case COLINEAR: {
        size_t n1 = i;
        size_t n2 = j;

        P2 q1 = points[i], q2 = points[j];

        if (q2 < q1) {
          std::swap(q1, q2);
          std::swap(n1, n2);
        }

        if (equal(q1, line.v1)) {
          out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, q1, n1));
        } else if (q1.x < line.v1.x) {
          out.push_back(PolyIntersectionInfo(INTERSECT_EDGE, line.v1, i));
        } else {
          out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, q1, n1));
        }
        if (equal(q2, line.v2)) {
          out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, q2, n2));
        } else if (line.v2.x < q2.x) {
          out.push_back(PolyIntersectionInfo(INTERSECT_EDGE, line.v2, i));
        } else {
          out.push_back(PolyIntersectionInfo(INTERSECT_VERTEX, q2, n2));
        }

        count += 2;

        break;
      }
      default:
        break;
    }
  }
  return count;
}

struct FwdSort {
  bool operator()(const PolyIntersectionInfo& a,
                  const PolyIntersectionInfo& b) const {
    return a.ipoint < b.ipoint;
  }
};

struct RevSort {
  bool operator()(const PolyIntersectionInfo& a,
                  const PolyIntersectionInfo& b) const {
    return a.ipoint < b.ipoint;
  }
};

int sortedLineSegmentPolyIntersections(const P2Vector& points,
                                       LineSegment2 line,
                                       std::vector<PolyIntersectionInfo>& out) {
  bool swapped = line.v2 < line.v1;

  int count = lineSegmentPolyIntersections(points, line, out);
  if (swapped) {
    std::sort(out.begin(), out.end(), RevSort());
  } else {
    std::sort(out.begin(), out.end(), FwdSort());
  }
  return count;
}

bool pickContainedPoint(const std::vector<P2>& poly, P2& result) {
  return pickContainedPoint(poly, p2_adapt_ident(), result);
}
}  // namespace geom2d
}  // namespace carve
