#include "entity_canvas.hpp"

#include "../math.hpp"

#include <algorithm>
#include <cmath>

namespace game::util::imgui::widget
{
  namespace
  {
    bool is_rect_valid(glm::vec4 rect)
    {
      return std::isfinite(rect.x) && std::isfinite(rect.y) && std::isfinite(rect.z) &&
             std::isfinite(rect.w) && rect.z > 0.0f && rect.w > 0.0f;
    }

    struct CanvasEffects
    {
      glm::vec2 shakeOffset{};
      int shakeTimer{};
      int shakeTimerMax{};
    };

    CanvasEffects canvas_effects_get(const Canvas& canvas)
    {
      return {.shakeOffset = canvas.shakeOffset,
              .shakeTimer = canvas.shakeTimer,
              .shakeTimerMax = canvas.shakeTimerMax};
    }

    void canvas_effects_apply(Canvas& canvas, const CanvasEffects& effects)
    {
      canvas.shakeOffset = effects.shakeOffset;
      canvas.shakeTimer = effects.shakeTimer;
      canvas.shakeTimerMax = effects.shakeTimerMax;
    }
  }

  void EntityCanvas::clear()
  {
    auto effects = canvas_effects_get(canvas);
    canvas = {};
    canvas_effects_apply(canvas, effects);
    rect = glm::vec4(NAN);
    imageSize = {};
    scale = {};
  }

  ImVec2 EntityCanvas::shake_offset_get() const
  {
    return ImVec2(canvas.shakeOffset.x * scale, canvas.shakeOffset.y * scale);
  }

  Canvas& EntityCanvas::render(Resources& resources, Entity& entity, ImVec2 size, float maxScale)
  {
    return render(resources, entity, size, maxScale, nullptr);
  }

  Canvas& EntityCanvas::render_rotated(Resources& resources, Entity& entity, ImVec2 size, float rotation,
                                       float maxScale)
  {
    return render(resources, entity, size, maxScale, &rotation);
  }

  Canvas& EntityCanvas::render(Resources& resources, Entity& entity, ImVec2 size, float maxScale,
                               const float* rotation)
  {
    if (!is_rect_valid(rect)) rect = entity.rect();

    auto rectSize = glm::vec2(rect.z, rect.w);
    scale = (size.x <= 0.0f || size.y <= 0.0f || !is_rect_valid(rect))
                ? 0.0f
                : std::min(size.x / rectSize.x, size.y / rectSize.y);
    if (maxScale > 0.0f) scale = std::min(maxScale, scale);

    auto previewSize = rectSize * scale;
    imageSize = ImVec2(previewSize.x, previewSize.y);
    auto canvasSize = glm::ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));

    if (!canvas.is_valid())
    {
      auto effects = canvas_effects_get(canvas);
      canvas = Canvas(canvasSize, Canvas::FLIP);
      canvas_effects_apply(canvas, effects);
    }

    canvas.zoom = math::to_percent(scale);
    canvas.pan = glm::vec2(rect.x, rect.y);
    canvas.bind();
    canvas.size_set(canvasSize);
    canvas.clear();

    if (rotation)
    {
      entity.overrides.emplace_back(-1, resource::xml::Anm2::ROOT, Entity::Override::SET,
                                    resource::xml::Anm2::FrameOptional{.rotation = *rotation});
      entity.render(resources.shaders[resource::shader::TEXTURE], resources.shaders[resource::shader::RECT],
                    canvas);
      entity.overrides.pop_back();
    }
    else
      entity.render(resources.shaders[resource::shader::TEXTURE], resources.shaders[resource::shader::RECT],
                    canvas);

    canvas.unbind();
    return canvas;
  }
}
