#include "common/math/GameMath.h"
#include <algorithm>
#include <cmath>

namespace frostbite2D::gameMath {
namespace {

constexpr double kPolygonEpsilon = 0.001;

} // namespace

int32 RoundWorldCoordinate(float value) {
  return static_cast<int32>(std::lround(value));
}

Vec2 MakeIntegerWorldPoint(int x, int y) {
  return Vec2(static_cast<float>(x), static_cast<float>(y));
}

Vec3 MakeIntegerWorldPosition(int x, int y, int z) {
  return Vec3(static_cast<float>(x), static_cast<float>(y),
              static_cast<float>(z));
}

bool IsPointOnSegment(const Vec2& point, const Vec2& start, const Vec2& end) {
  Vec2 segment = end - start;
  Vec2 toPoint = point - start;
  double cross = static_cast<double>(segment.cross(toPoint));
  if (std::abs(cross) > kPolygonEpsilon) {
    return false;
  }

  double minX = std::min(start.x, end.x) - kPolygonEpsilon;
  double maxX = std::max(start.x, end.x) + kPolygonEpsilon;
  double minY = std::min(start.y, end.y) - kPolygonEpsilon;
  double maxY = std::max(start.y, end.y) + kPolygonEpsilon;
  return point.x >= minX && point.x <= maxX && point.y >= minY &&
         point.y <= maxY;
}

bool IsPointInPolygon(const std::vector<Vec2>& polygon, const Vec2& point) {
  if (polygon.size() < 3) {
    return false;
  }

  bool inside = false;
  for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
    const Vec2& start = polygon[j];
    const Vec2& end = polygon[i];
    if (IsPointOnSegment(point, start, end)) {
      return true;
    }

    bool crossesScanline = (start.y > point.y) != (end.y > point.y);
    if (!crossesScanline) {
      continue;
    }

    double intersectX =
        static_cast<double>(start.x) +
        (static_cast<double>(point.y) - static_cast<double>(start.y)) *
            (static_cast<double>(end.x) - static_cast<double>(start.x)) /
            (static_cast<double>(end.y) - static_cast<double>(start.y));
    if (intersectX >= static_cast<double>(point.x) - kPolygonEpsilon) {
      inside = !inside;
    }
  }

  return inside;
}

bool IsPointMovable(const std::vector<Vec2>& polygon, int x, int y) {
  return IsPointInPolygon(polygon, MakeIntegerWorldPoint(x, y));
}

std::vector<Vec2> BuildRectPolygon(const Rect& rect) {
  if (rect.empty()) {
    return {};
  }

  return {
      Vec2(rect.left(), rect.top()),
      Vec2(rect.right(), rect.top()),
      Vec2(rect.right(), rect.bottom()),
      Vec2(rect.left(), rect.bottom()),
  };
}

std::vector<Rect> BuildPolygonFillRects(const std::vector<Vec2>& polygon) {
  std::vector<Rect> fillRects;
  if (polygon.size() < 3) {
    return fillRects;
  }

  float minY = polygon.front().y;
  float maxY = polygon.front().y;
  for (const auto& point : polygon) {
    minY = std::min(minY, point.y);
    maxY = std::max(maxY, point.y);
  }

  int scanlineMinY = static_cast<int>(std::floor(minY));
  int scanlineMaxY = static_cast<int>(std::ceil(maxY)) - 1;
  if (scanlineMaxY < scanlineMinY) {
    return fillRects;
  }

  for (int y = scanlineMinY; y <= scanlineMaxY; ++y) {
    float scanY = static_cast<float>(y) + 0.5f;
    std::vector<float> intersections;
    intersections.reserve(polygon.size());

    for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
      const Vec2& start = polygon[j];
      const Vec2& end = polygon[i];
      if (start.y == end.y) {
        continue;
      }

      float edgeMinY = std::min(start.y, end.y);
      float edgeMaxY = std::max(start.y, end.y);
      if (scanY < edgeMinY || scanY >= edgeMaxY) {
        continue;
      }

      float t = (scanY - start.y) / (end.y - start.y);
      intersections.push_back(start.x + (end.x - start.x) * t);
    }

    if (intersections.size() < 2) {
      continue;
    }

    std::sort(intersections.begin(), intersections.end());
    for (size_t i = 0; i + 1 < intersections.size(); i += 2) {
      float left = std::floor(intersections[i]);
      float right = std::ceil(intersections[i + 1]);
      float width = right - left;
      if (width > 0.0f) {
        fillRects.emplace_back(left, static_cast<float>(y), width, 1.0f);
      }
    }
  }

  return fillRects;
}

} // namespace frostbite2D::gameMath
