#pragma once
#include "config.hpp"
#include "constants.hpp"
#include "dinkyecs.hpp"
#include "point.hpp"
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Shader.hpp>
#include <SFML/System/Vector2.hpp>
#include <functional>
#include <optional>
#include "easings.hpp"
#include "json_mods.hpp"
#include "goap.hpp"

namespace components {
  using std::string;
  using namespace nlohmann;

  struct InventoryItem {
    int count;
    json data;
  };

  struct SpriteEffect {
    int frames;
    std::shared_ptr<sf::Shader> effect;
  };

  struct Position {
    Point location{0,0};
    Point aiming_at{0,0};
  };

  struct Motion {
    int dx;
    int dy;
    bool random=false;
  };

  struct Tile {
    wchar_t display;
    std::string foreground;
    std::string background;
  };

  struct GameConfig {
    Config game;
    Config enemies;
    Config items;
    Config tiles;
    Config devices;
    Config bosses;
    Config rituals;
  };

  struct Personality {
    int hearing_distance = 10;
    bool tough = true;
  };

  struct EnemyConfig {
    std::string ai_script;
    std::string ai_start_name;
    std::string ai_goal_name;
  };

  struct Curative {
    int hp = 10;
  };

  struct BossFight {
    std::string background;
    std::optional<std::string> stage;
    std::string weapon_sound;
  };

  struct Combat {
    int hp;
    int max_hp;
    int damage;

    /* NOTE: This is used to _mark_ entities as dead, to detect ones that have just died. Don't make attack automatically set it.*/
    bool dead = false;

    int attack(Combat &target);
  };

  struct LightSource {
    int strength = 0;
    float radius = 1.0f;
  };

  struct Device {
    json config;
    std::vector<std::string> events;
  };

  struct Sprite {
    string name;
    int width;
    int height;
    float scale;
  };

  struct Sound {
    std::string attack;
    std::string death;
  };

  struct Animation {
    float scale = 0.0f;
    bool simple = true;
    int frames = 10;
    float speed = 0.3f;
    ease::Style easing = ease::IN_OUT_BACK;
    float ease_rate = 0.5f;
    bool stationary = false;
    int current = 0;
    bool playing = false;
    float subframe = 0.0f;
    bool looped = false;
    // BUG: this is weirdly not used in most animations but also named wrong should be frame_width
    int frame_width = TEXTURE_WIDTH;

    void play();
    float twitching();
    void step(sf::Vector2f& scale_out, sf::Vector2f& pos_out, sf::IntRect& rect_out);
  };

  struct Player {
    DinkyECS::Entity entity;
  };

  template <typename T> struct NameOf;

  using ReflFuncSignature = std::function<void(DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j)>;

  using ComponentMap = std::unordered_map<std::string, ReflFuncSignature>;

  ENROLL_COMPONENT(Tile, display, foreground, background);
  ENROLL_COMPONENT(BossFight, background, stage, weapon_sound);
  ENROLL_COMPONENT(Sprite, name, width, height, scale);
  ENROLL_COMPONENT(Curative, hp);
  ENROLL_COMPONENT(LightSource, strength, radius);
  ENROLL_COMPONENT(Position, location.x, location.y);
  ENROLL_COMPONENT(EnemyConfig, ai_script, ai_start_name, ai_goal_name);
  ENROLL_COMPONENT(Personality, hearing_distance, tough);
  ENROLL_COMPONENT(Motion, dx, dy, random);
  ENROLL_COMPONENT(Combat, hp, max_hp, damage, dead);
  ENROLL_COMPONENT(Device, config, events);
  ENROLL_COMPONENT(Animation, scale, simple, frames,
      speed, easing, ease_rate, stationary);
  ENROLL_COMPONENT(Sound, attack, death);

  template<typename COMPONENT> COMPONENT convert(nlohmann::json &data) {
    COMPONENT result;
    from_json(data, result);
    return result;
  }

  template<typename COMPONENT> COMPONENT get(nlohmann::json &data) {
    for (auto &i : data["components"]) {
      if(i["_type"] == NameOf<COMPONENT>::name) {
        return convert<COMPONENT>(i);
      }
    }

    return {};
  }

  template <typename COMPONENT> void enroll(ComponentMap &m) {
    m[NameOf<COMPONENT>::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) {
      COMPONENT c;
      from_json(j, c);
      world.set<COMPONENT>(ent, c);
    };
  }


  void init();

  void configure_entity(DinkyECS::World& world, DinkyECS::Entity ent, json& data);

}
