#pragma once

#include <mapbox/geometry/line_string.hpp>
#include <mapbox/geometry/point.hpp>
#include <mapbox/geometry/polygon.hpp>
#include <mapbox/geometry/wagyu/config.hpp>
#include <mapbox/geometry/wagyu/edge.hpp>
#include <mapbox/geometry/wagyu/util.hpp>

namespace mapbox {
namespace geometry {
namespace wagyu {

template <typename T>
bool point_2_is_between_point_1_and_point_3(
    mapbox::geometry::point<T> const& pt1,
    mapbox::geometry::point<T> const& pt2,
    mapbox::geometry::point<T> const& pt3) {
  if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) {
    return false;
  } else if (pt1.x != pt3.x) {
    return (pt2.x > pt1.x) == (pt2.x < pt3.x);
  } else {
    return (pt2.y > pt1.y) == (pt2.y < pt3.y);
  }
}

template <typename T1, typename T2>
bool build_edge_list(mapbox::geometry::linear_ring<T2> const& path_geometry,
                     edge_list<T1>& edges) {
  if (path_geometry.size() < 3) {
    return false;
  }

  // As this is a loop, we need to first go backwards from end to try and find
  // the proper starting point for the iterators before the beginning

  auto itr_rev = path_geometry.rbegin();
  auto itr = path_geometry.begin();
  mapbox::geometry::point<T2> pt1 = *itr_rev;
  mapbox::geometry::point<T2> pt2 = *itr;

  // Find next non repeated point going backwards from
  // end for pt1
  while (pt1 == pt2) {
    ++itr_rev;
    if (itr_rev == path_geometry.rend()) {
      return false;
    }
    pt1 = *itr_rev;
  }
  ++itr;
  mapbox::geometry::point<T2> pt3 = *itr;
  auto itr_last = itr_rev.base();
  mapbox::geometry::point<T2> front_pt;
  mapbox::geometry::point<T2> back_pt;
  while (true) {
    if (pt3 == pt2) {
      // Duplicate point advance itr, but do not
      // advance other points
      if (itr == itr_last) {
        break;
      }
      ++itr;
      if (itr == itr_last) {
        if (edges.empty()) {
          break;
        }
        pt3 = front_pt;
      } else {
        pt3 = *itr;
      }
      continue;
    }

    // Now check if slopes are equal between two segments - either
    // a spike or a collinear point - if so drop point number 2.
    if (slopes_equal(pt1, pt2, pt3)) {
      // We need to reconsider previously added points
      // because the point it was using was found to be collinear
      // or a spike
      pt2 = pt1;
      if (!edges.empty()) {
        edges.pop_back();  // remove previous edge (pt1)
      }
      if (!edges.empty()) {
        auto const& back_top = edges.back().top;
        if (static_cast<T1>(back_pt.x) == back_top.x &&
            static_cast<T1>(back_pt.y) == back_top.y) {
          auto const& back_bot = edges.back().bot;
          pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_bot.x),
                                            static_cast<T2>(back_bot.y));
        } else {
          pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_top.x),
                                            static_cast<T2>(back_top.y));
        }
        back_pt = pt1;
      } else {
        // If this occurs we must look to the back of the
        // ring for new points.
        while (*itr_rev == pt2) {
          ++itr_rev;
          if ((itr + 1) == itr_rev.base()) {
            return false;
          }
        }
        pt1 = *itr_rev;
        itr_last = itr_rev.base();
      }
      continue;
    }

    if (edges.empty()) {
      front_pt = pt2;
    }
    edges.emplace_back(pt2, pt3);
    back_pt = pt2;
    if (itr == itr_last) {
      break;
    }
    pt1 = pt2;
    pt2 = pt3;
    ++itr;
    if (itr == itr_last) {
      if (edges.empty()) {
        break;
      }
      pt3 = front_pt;
    } else {
      pt3 = *itr;
    }
  }

  bool modified = false;
  do {
    modified = false;
    if (edges.size() < 3) {
      return false;
    }
    auto& f = edges.front();
    auto& b = edges.back();
    if (slopes_equal(f, b)) {
      if (f.bot == b.top) {
        if (f.top == b.bot) {
          edges.pop_back();
          edges.erase(edges.begin());
        } else {
          f.bot = b.bot;
          edges.pop_back();
        }
        modified = true;
      } else if (f.top == b.bot) {
        f.top = b.top;
        edges.pop_back();
        modified = true;
      } else if (f.top == b.top && f.bot == b.bot) {
        edges.pop_back();
        edges.erase(edges.begin());
        modified = true;
      } else if (f.top == b.top) {
        if (point_2_is_between_point_1_and_point_3(f.top, f.bot, b.bot)) {
          b.top = f.bot;
          edges.erase(edges.begin());
        } else {
          f.top = b.bot;
          edges.pop_back();
        }
        modified = true;
      } else if (f.bot == b.bot) {
        if (point_2_is_between_point_1_and_point_3(f.bot, f.top, b.top)) {
          b.bot = f.top;
          edges.erase(edges.begin());
        } else {
          f.bot = b.top;
          edges.pop_back();
        }
        modified = true;
      }
    }
  } while (modified);

  return true;
}
}  // namespace wagyu
}  // namespace geometry
}  // namespace mapbox
