#include "animation.h"

#include "map_.h"
#include "math_.h"
#include "unordered_map_.h"
#include "xml_.h"
#include <ranges>

using namespace anm2ed::util;
using namespace glm;
using namespace tinyxml2;

namespace anm2ed::anm2
{
  Animation::Animation(XMLElement* element)
  {
    int id{};

    xml::query_string_attribute(element, "Name", &name);
    element->QueryIntAttribute("FrameNum", &frameNum);
    element->QueryBoolAttribute("Loop", &isLoop);

    if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
      rootAnimation = Item(rootAnimationElement, ROOT);

    if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
    {
      for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child;
           child = child->NextSiblingElement("LayerAnimation"))
      {
        layerAnimations[id] = Item(child, LAYER, &id);
        layerOrder.push_back(id);
      }
    }

    if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
      for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
           child = child->NextSiblingElement("NullAnimation"))
        nullAnimations[id] = Item(child, NULL_, &id);

    if (auto triggersElement = element->FirstChildElement("Triggers")) triggers = Item(triggersElement, TRIGGER);
  }

  Item* Animation::item_get(Type type, int id)
  {
    switch (type)
    {
      case ROOT:
        return &rootAnimation;
      case LAYER:
        return unordered_map::find(layerAnimations, id);
      case NULL_:
        return map::find(nullAnimations, id);
      case TRIGGER:
        return &triggers;
      default:
        return nullptr;
    }
    return nullptr;
  }

  void Animation::item_remove(Type type, int id)
  {
    switch (type)
    {
      case LAYER:
        layerAnimations.erase(id);
        for (auto [i, value] : std::views::enumerate(layerOrder))
          if (value == id) layerOrder.erase(layerOrder.begin() + i);
        break;
      case NULL_:
        nullAnimations.erase(id);
        break;
      case ROOT:
      case TRIGGER:
      default:
        break;
    }
  }

  XMLElement* Animation::to_element(XMLDocument& document)
  {
    auto element = document.NewElement("Animation");
    element->SetAttribute("Name", name.c_str());
    element->SetAttribute("FrameNum", frameNum);
    element->SetAttribute("Loop", isLoop);

    rootAnimation.serialize(document, element, ROOT);

    auto layerAnimationsElement = document.NewElement("LayerAnimations");
    for (auto& i : layerOrder)
    {
      Item& layerAnimation = layerAnimations.at(i);
      layerAnimation.serialize(document, layerAnimationsElement, LAYER, i);
    }
    element->InsertEndChild(layerAnimationsElement);

    auto nullAnimationsElement = document.NewElement("NullAnimations");
    for (auto& [id, nullAnimation] : nullAnimations)
      nullAnimation.serialize(document, nullAnimationsElement, NULL_, id);
    element->InsertEndChild(nullAnimationsElement);

    triggers.serialize(document, element, TRIGGER);

    return element;
  }

  void Animation::serialize(XMLDocument& document, XMLElement* parent) { parent->InsertEndChild(to_element(document)); }

  std::string Animation::to_string()
  {
    XMLDocument document{};
    document.InsertEndChild(to_element(document));
    return xml::document_to_string(document);
  }

  int Animation::length()
  {
    int length{};

    if (int rootAnimationLength = rootAnimation.length(ROOT); rootAnimationLength > length)
      length = rootAnimationLength;

    for (auto& layerAnimation : layerAnimations | std::views::values)
      if (int layerAnimationLength = layerAnimation.length(LAYER); layerAnimationLength > length)
        length = layerAnimationLength;

    for (auto& nullAnimation : nullAnimations | std::views::values)
      if (int nullAnimationLength = nullAnimation.length(NULL_); nullAnimationLength > length)
        length = nullAnimationLength;

    if (int triggersLength = triggers.length(TRIGGER); triggersLength > length) length = triggersLength;

    return length;
  }

  void Animation::fit_length() { frameNum = length(); }

  vec4 Animation::rect(bool isRootTransform)
  {
    constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};

    float minX = std::numeric_limits<float>::infinity();
    float minY = std::numeric_limits<float>::infinity();
    float maxX = -std::numeric_limits<float>::infinity();
    float maxY = -std::numeric_limits<float>::infinity();
    bool any = false;

    for (float t = 0.0f; t < (float)frameNum; t += 1.0f)
    {
      mat4 transform(1.0f);

      if (isRootTransform)
      {
        auto root = rootAnimation.frame_generate(t, ROOT);
        transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
      }

      for (auto& [id, layerAnimation] : layerAnimations)
      {
        auto frame = layerAnimation.frame_generate(t, LAYER);

        if (frame.size == vec2() || !frame.isVisible) continue;

        auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot,
                                                               math::percent_to_unit(frame.scale), frame.rotation);
        for (auto& corner : CORNERS)
        {
          vec4 world = layerTransform * vec4(corner, 0.0f, 1.0f);
          minX = std::min(minX, world.x);
          minY = std::min(minY, world.y);
          maxX = std::max(maxX, world.x);
          maxY = std::max(maxY, world.y);
          any = true;
        }
      }
    }

    if (!any) return vec4(-1.0f);
    return {minX, minY, maxX - minX, maxY - minY};
  }
}