#include "map/GameMapLayer.h"
#include "common/math/GameMath.h"
#include <algorithm>
#include <cmath>
#include <frostbite2D/graphics/renderer.h>

namespace frostbite2D {

namespace {

constexpr float kDebugAreaAlpha = 0.45f;
constexpr float kDebugOutlineAlpha = 0.85f;
constexpr float kDebugEdgePointSize = 5.0f;
constexpr float kDebugVertexSize = 9.0f;
constexpr float kDebugEdgeStep = 4.0f;

void DrawPolygonOutline(const std::vector<Vec2>& polygon, const Vec2& worldOrigin,
                        const Color& color) {
  if (polygon.size() < 2) {
    return;
  }

  auto& renderer = Renderer::get();
  for (size_t i = 0; i < polygon.size(); ++i) {
    Vec2 start = worldOrigin + polygon[i];
    Vec2 end = worldOrigin + polygon[(i + 1) % polygon.size()];
    Vec2 delta = end - start;
    float length = delta.length();
    int steps = std::max(1, static_cast<int>(std::ceil(length / kDebugEdgeStep)));

    for (int step = 0; step <= steps; ++step) {
      float t = static_cast<float>(step) / static_cast<float>(steps);
      Vec2 point = start + delta * t;
      renderer.drawQuad(
          Rect(point.x - kDebugEdgePointSize * 0.5f,
               point.y - kDebugEdgePointSize * 0.5f, kDebugEdgePointSize,
               kDebugEdgePointSize),
          color);
    }
  }
}

void DrawPolygonVertices(const std::vector<Vec2>& polygon,
                         const Vec2& worldOrigin, const Color& color) {
  auto& renderer = Renderer::get();
  for (const auto& vertex : polygon) {
    Vec2 point = worldOrigin + vertex;
    renderer.drawQuad(
        Rect(point.x - kDebugVertexSize * 0.5f,
             point.y - kDebugVertexSize * 0.5f, kDebugVertexSize,
             kDebugVertexSize),
        color);
  }
}

} // namespace

void GameMapLayer::Render() {
  Actor::Render();

  Vec2 worldOrigin = GetWorldTransform().transformPoint(Vec2::Zero());

  for (const auto& rect : feasibleAreaFillRects_) {
    Rect drawRect(worldOrigin.x + rect.origin.x, worldOrigin.y + rect.origin.y,
                  rect.width(), rect.height());
    Renderer::get().drawQuad(drawRect, Color(0.0f, 1.0f, 0.0f, kDebugAreaAlpha));
  }

  for (const auto& moveArea : moveAreaInfoList_) {
    bool isHighlighted = moveArea.index == highlightedMoveAreaIndex_;
    Color fillColor =
        isHighlighted ? Color(0.0f, 1.0f, 1.0f, 0.60f)
                      : Color(0.0f, 0.0f, 1.0f, kDebugAreaAlpha);
    Rect drawRect(worldOrigin.x + moveArea.rect.origin.x,
                  worldOrigin.y + moveArea.rect.origin.y,
                  moveArea.rect.width(), moveArea.rect.height());
    Renderer::get().drawQuad(drawRect, fillColor);
  }

  if (!feasibleAreaPolygon_.empty()) {
    Color outlineColor(0.0f, 1.0f, 0.0f, kDebugOutlineAlpha);
    DrawPolygonOutline(feasibleAreaPolygon_, worldOrigin, outlineColor);
    DrawPolygonVertices(feasibleAreaPolygon_, worldOrigin, outlineColor);
  }

  for (auto& moveArea : moveAreaInfoList_) {
    std::vector<Vec2> polygon = gameMath::BuildRectPolygon(moveArea.rect);
    if (polygon.empty()) {
      continue;
    }
    bool isHighlighted = moveArea.index == highlightedMoveAreaIndex_;
    Color moveAreaOutlineColor =
        isHighlighted ? Color(0.0f, 1.0f, 1.0f, 1.0f)
                      : Color(0.0f, 0.0f, 1.0f, kDebugOutlineAlpha);
    DrawPolygonOutline(polygon, worldOrigin, moveAreaOutlineColor);
    DrawPolygonVertices(polygon, worldOrigin, moveAreaOutlineColor);
  }
}

void GameMapLayer::SetDebugFeasibleAreaPolygon(const std::vector<Vec2>& polygon) {
  feasibleAreaPolygon_ = polygon;
  feasibleAreaFillRects_ = gameMath::BuildPolygonFillRects(feasibleAreaPolygon_);
}

void GameMapLayer::AddDebugMoveAreaInfo(const Rect& rect, size_t index) {
  DebugMoveAreaInfo debugArea;
  debugArea.rect = rect;
  debugArea.index = index;

  moveAreaInfoList_.push_back(std::move(debugArea));
}

void GameMapLayer::SetDebugHighlightedMoveAreaIndex(size_t index) {
  highlightedMoveAreaIndex_ = index;
}

void GameMapLayer::ClearDebugAreaInfo() {
  feasibleAreaPolygon_.clear();
  feasibleAreaFillRects_.clear();
  moveAreaInfoList_.clear();
  highlightedMoveAreaIndex_ = kInvalidMoveAreaIndex;
}

void GameMapLayer::AddObject(RefPtr<Actor> obj) {
  AddChild(obj);
}

} // namespace frostbite2D
