#pragma once

#include <unordered_set>

#include "render/canvas.hpp"
#include "resource/character.hpp"
#include "util/measurement.hpp"

namespace game
{
  enum EntityType
  {
    ACTOR,
    CHARACTER,
    CURSOR,
    AUTOCURSOR,
    ITEM,
    PARTICLE
  };

  class Entity
  {
  public:
    using Anm2 = resource::xml::Anm2;
    using FrameOptional = Anm2::FrameOptional;

    static constexpr auto UPDATE_RATE = 60.0f;
    static constexpr auto DIGESTION_MAX = 100.0f;

    enum Mode
    {
      PLAY,
      PLAY_FORCE,
      SET,
    };

    enum State
    {
      STOPPED,
      PLAYING
    };

    enum class Transition
    {
      NOW,
      AFTER_CURRENT,
      IF_IDLE
    };

    enum class Interrupt
    {
      IF_ALLOWED,
      ALWAYS,
      NEVER
    };

    enum CursorState
    {
      DEFAULT,
      HOVER,
      ACTION
    };

    enum SpritesheetType
    {
      NORMAL,
      ALTERNATE
    };

    class Override
    {
    public:
      enum Mode
      {
        SET,
        ADD
      };

      using Function = void (*)(Override&);

      int id{-1};
      Anm2::Type type{Anm2::NONE};
      Mode mode{SET};
      FrameOptional frame{};
      std::optional<float> time{};
      Function function{nullptr};

      FrameOptional frameBase{};
      std::optional<float> timeStart{};

      float cycles{};

      Override() = default;
      Override(int, Anm2::Type, Mode, FrameOptional = {}, std::optional<float> = std::nullopt, Function = nullptr,
               float = 0);
    };

    struct AnimationRequest
    {
      std::string animation{};
      std::string appendID{};
      int index{-1};
      float time{};
      float speedMultiplier{1.0f};
      Mode mode{PLAY};
      Transition transition{Transition::NOW};
      Interrupt interrupt{Interrupt::IF_ALLOWED};

      inline bool empty() const { return animation.empty() && index == -1; };
    };

    EntityType entityType{ACTOR};

    int fps{30};
    std::map<int, Anm2::Spritesheet> spritesheets{};
    std::map<int, Anm2::Layer> layers{};
    std::map<int, Anm2::Null> nulls{};
    std::map<int, Anm2::Event> events{};
    std::map<int, Anm2::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<Anm2::Animation> animations{};
    std::unordered_map<std::string, int> animationMap{};
    std::unordered_map<int, std::string> animationMapReverse{};
    std::unordered_set<std::string> invalidAnimations{};
    std::string path{};
    bool isValid{};
    Anm2::Flags flags{};

    State state{STOPPED};
    Mode mode{PLAY};
    glm::vec2 position{};
    float time{};
    bool isShowNulls{};
    bool isVisible{true};
    int animationIndex{-1};
    int playedEventID{-1};
    float startTime{};
    float speedMultiplier{};
    AnimationRequest pendingAnimation{};
    AnimationRequest currentAnimation{};
    AnimationRequest nextAnimation{};
    std::unordered_set<int> playedTriggers{};
    std::vector<Override> overrides{};

    resource::xml::Character data;
    float weight{};
    int stage{0};
    float calories{};
    float capacity{};
    float digestionProgress{};
    float digestionRate{0.05f};
    int digestionTimer{};
    bool isDigesting{};
    bool isJustDigested{};
    float eatSpeed{1.0f};
    float totalCaloriesConsumed{};
    int totalFoodItemsEaten{};
    int talkOverrideID{};
    float talkTimer{};
    bool isTalking{};
    std::vector<int> animationTalkDurations{};
    int blinkOverrideID{};
    float blinkTimer{};
    bool isBlinking{};
    std::vector<int> animationBlinkDurations{};
    bool isStageUp{};
    bool isStageUpDuring{};
    bool isJustStageUp{};
    bool isJustStageFinal{};
    SpritesheetType spritesheetType{};
    std::map<int, Override> interactAreaOverrides{};
    std::unordered_map<int, int> expandAreaOverrideLayerIDs{};
    std::unordered_map<int, int> expandAreaOverrideNullIDs{};

    CursorState cursorState{DEFAULT};
    int interactTypeID{-1};
    std::optional<glm::vec2> positionOverride{};

    bool isToBeDeleted{};
    bool isRemoveOnAnimationEnd{};
    bool isHeld{};
    int schemaID{};
    int rotationOverrideID{};
    int durability{};
    glm::vec2 velocity{};
    float angularVelocity{};
    float phase{};
    float magnetTime{};
    bool isCollected{};
    bool isMagnetized{};

    Entity() = default;
    Entity(const Entity&);
    Entity(Entity&&) noexcept;
    Entity& operator=(const Entity&);
    Entity& operator=(Entity&&) noexcept;
    Entity(resource::xml::Anm2, glm::vec2 position = {}, Mode = PLAY, float time = 0.0f, int animationIndex = -1);
    Entity(resource::xml::Character&, glm::ivec2);
    Entity(resource::xml::Anm2, glm::ivec2 position, int id, int durability = 0, int animationIndex = -1,
           glm::vec2 velocity = {}, float rotation = 0.0f);

    bool is_playing(const std::string& name = {});
    glm::vec4 null_frame_rect(int = -1, int animationIndex = -1, float time = -1.0f);
    glm::vec4 rect();
    int animation_index_get(const std::string&);
    resource::xml::Anm2::Animation* animation_get(const std::string&);
    resource::xml::Anm2::Animation* animation_get(int = -1);
    resource::xml::Anm2::Frame frame_generate(resource::xml::Anm2::Item&, float, resource::xml::Anm2::Type,
                                              int id = -1);
    resource::xml::Anm2::Item* item_get(resource::xml::Anm2::Type, int = -1, int = -1);
    void consume_played_event();
    void play(AnimationRequest);
    bool is_animation_request() const;
    bool is_animation_finished();
    void render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas&);

    float weight_get(util::measurement::System = util::measurement::METRIC);
    float digestion_rate_get();
    float capacity_percent_get() const;
    float max_capacity() const;
    bool is_over_capacity() const;
    bool is_max_capacity() const;
    int stage_get() const;
    int stage_from_weight_get(float weight) const;
    int stage_max_get() const;
    int stage_count_get() const;
    float stage_progress_get();
    float stage_threshold_get(int stage = -1, util::measurement::System = util::measurement::METRIC) const;
    float stage_threshold_next_get(util::measurement::System = util::measurement::METRIC) const;
    void expand_areas_apply();
    void spritesheet_set(SpritesheetType);
    std::string idle_animation_get() const;
    std::string animation_append_id_get() const;

    void update();

  private:
    void anm2_set(const resource::xml::Anm2&);
    void animation_start(int, const AnimationRequest&);
    bool animation_request_apply(AnimationRequest&);
    void pending_animation_apply();
    void animation_update();
    void character_values_clamp();
    void character_update();
  };

}
