#pragma once

#include <functional>
#include <optional>
#include <tinyxml2/tinyxml2.h>

#include <filesystem>
#include <map>
#include <string_view>
#include <string>
#include <unordered_map>
#include <vector>

#include <glm/glm.hpp>

#include "audio.hpp"
#include "texture.hpp"

#include "../util/physfs.hpp"

namespace game::resource::xml
{
  class Anm2
  {
  public:
    enum Type
    {
      NONE,
      ROOT,
      LAYER,
      NULL_,
      TRIGGER
    };

    enum Flag
    {
      NO_SPRITESHEETS = (1 << 0),
      NO_SOUNDS = (1 << 1),
      DEFAULT_ANIMATION_ONLY = (1 << 2)
    };

    using Flags = int;

    struct Element
    {
      enum Type
      {
        UNKNOWN,
        ACTOR,
        INFO,
        CONTENT,
        SPRITESHEETS,
        SPRITESHEET,
        REGION,
        LAYERS,
        LAYER_ELEMENT,
        NULLS,
        NULL_ELEMENT,
        EVENTS,
        EVENT,
        SOUNDS,
        SOUND_ELEMENT,
        ANIMATIONS,
        ANIMATION,
        ROOT_ANIMATION,
        LAYER_ANIMATIONS,
        LAYER_ANIMATION,
        NULL_ANIMATIONS,
        NULL_ANIMATION,
        GROUP,
        TRIGGERS,
        TRIGGER_ELEMENT,
        FRAME,
        TYPE_COUNT
      };

      Type type{UNKNOWN};
      std::vector<Element> children{};

      std::string createdBy{};
      std::string createdOn{};
      std::string defaultAnimation{};
      std::string name{};
      std::string path{};

      int id{-1};
      int layerID{-1};
      int nullID{-1};
      int spritesheetID{-1};
      int eventID{-1};
      int regionID{-1};
      int groupID{-1};
      int atFrame{-1};
      int frameNum{};
      int fps{30};
      int version{};
      int delay{1};

      glm::vec2 crop{};
      glm::vec2 position{};
      glm::vec2 pivot{};
      glm::vec2 size{};
      glm::vec2 scale{100.0f, 100.0f};
      float rotation{};
      glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f};
      glm::vec3 colorOffset{};

      bool isVisible{true};
      bool isShowRect{};
      bool isLoop{};
      bool isExpanded{true};

      Element* child_get(Type, std::size_t index = 0);
      const Element* child_get(Type, std::size_t index = 0) const;
      std::vector<Element*> children_get(Type);
      std::vector<const Element*> children_get(Type) const;
      static Type type_get(std::string_view);
    };

    struct Spritesheet
    {
      struct Region
      {
        enum Origin
        {
          TOP_LEFT,
          CENTER,
          CUSTOM
        };

        std::string name{};
        glm::vec2 crop{};
        glm::vec2 pivot{};
        glm::vec2 size{};
        Origin origin{CUSTOM};
      };

      std::string path{};
      resource::Texture texture{};

      std::map<int, Region> regions{};
      std::vector<int> regionOrder{};
    };

    struct Sound
    {
      std::string path{};
      resource::Audio audio{};
    };

    struct Layer
    {
      std::string name{"New Layer"};
      int spritesheetID{-1};
    };

    struct Null
    {
      std::string name{"New Null"};
      bool isShowRect{};
    };

    struct Event
    {
      std::string name{"New Event"};
    };

    struct Frame
    {
      enum Interpolation
      {
        INTERPOLATION_NONE,
        LINEAR,
        EASE_IN,
        EASE_OUT,
        EASE_IN_OUT
      };

      glm::vec2 crop{};
      glm::vec2 position{};
      glm::vec2 pivot{};
      glm::vec2 size{};
      glm::vec2 scale{100, 100};
      float rotation{};
      int duration{1};
      glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f};
      glm::vec3 colorOffset{};
      bool isInterpolated{};
      Interpolation interpolation{INTERPOLATION_NONE};
      int eventID{-1};
      int regionID{-1};
      std::vector<int> soundIDs{};
      int atFrame{-1};

      bool isVisible{true};
    };

    struct FrameOptional
    {
      struct Vec2
      {
        std::optional<float> x{};
        std::optional<float> y{};

        Vec2() = default;
        Vec2(const glm::vec2& value) : x(value.x), y(value.y) {}
        Vec2& operator=(const glm::vec2& value)
        {
          x = value.x;
          y = value.y;
          return *this;
        }
      };

      struct Vec3
      {
        std::optional<float> x{};
        std::optional<float> y{};
        std::optional<float> z{};

        Vec3() = default;
        Vec3(const glm::vec3& value) : x(value.x), y(value.y), z(value.z) {}
        Vec3& operator=(const glm::vec3& value)
        {
          x = value.x;
          y = value.y;
          z = value.z;
          return *this;
        }
      };

      struct Vec4
      {
        std::optional<float> x{};
        std::optional<float> y{};
        std::optional<float> z{};
        std::optional<float> w{};

        Vec4() = default;
        Vec4(const glm::vec4& value) : x(value.x), y(value.y), z(value.z), w(value.w) {}
        Vec4& operator=(const glm::vec4& value)
        {
          x = value.x;
          y = value.y;
          z = value.z;
          w = value.w;
          return *this;
        }
      };

      Vec2 crop{};
      Vec2 position{};
      Vec2 pivot{};
      Vec2 size{};
      Vec2 scale{};
      std::optional<float> rotation{};
      Vec4 tint{};
      Vec3 colorOffset{};
      std::optional<bool> isInterpolated{};
      std::optional<Frame::Interpolation> interpolation{};
      std::optional<bool> isVisible{};
    };

    struct Item
    {
      std::vector<Frame> frames{};
      bool isVisible{};
    };

    struct Animation
    {
      std::string name{"New Animation"};
      int frameNum{};
      bool isLoop{};

      Item rootAnimation{};
      std::unordered_map<int, Item> layerAnimations{};
      std::vector<int> layerOrder{};
      std::map<int, Item> nullAnimations{};
      Item triggers{};
    };

    int fps{30};

    std::map<int, Spritesheet> spritesheets{};
    std::map<int, Layer> layers{};
    std::map<int, Null> nulls{};
    std::map<int, Event> events{};
    std::map<int, Sound> sounds{};

    std::unordered_map<std::string, int> layerMap{};
    std::unordered_map<std::string, int> nullMap{};
    std::unordered_map<std::string, int> eventMap{};

    std::string defaultAnimation{};
    int defaultAnimationID{-1};
    std::vector<Animation> animations{};
    std::unordered_map<std::string, int> animationMap{};
    std::unordered_map<int, std::string> animationMapReverse{};
    std::string path{};
    bool isValid{};
    Flags flags{};
    Element root{};

    Anm2() = default;
    Anm2(const Anm2&);
    Anm2(Anm2&&);
    Anm2& operator=(const Anm2&);
    Anm2& operator=(Anm2&&);
    Anm2(const std::filesystem::path&, Flags = 0);
    Anm2(const util::physfs::Path&, Flags = 0);

    bool is_valid() const;
    static int item_length(const Item*);
    static float interpolation_apply(Frame::Interpolation, float);
    static Frame frame_generate(Item&, float, Type = NONE, int id = -1,
                                const std::function<void(Frame&, Type, int)>& = {});

  private:
    void init(tinyxml2::XMLDocument& document, Flags anm2Flags, const util::physfs::Path& archive = {});
  };
}
