#pragma once
#include "game/config.hpp"
#include "constants.hpp"
#include "algos/dinkyecs.hpp"
#include "algos/point.hpp"
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Shader.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/View.hpp>
#include <SFML/System/Vector2.hpp>
#include <functional>
#include <optional>
#include "game/json_mods.hpp"
#include "ai/goap.hpp"
#include <array>

namespace combat {
  enum class BattleHostState;
}

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

  struct CombatResult {
    DinkyECS::Entity attacker;
    combat::BattleHostState host_state;
    int player_did = 0;
    int enemy_did = 0;
  };

  struct InventoryItem {
    int count;
    json data;
  };

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

  struct Temporary {
    bool is = true;
  };

  struct Collision {
    bool has = true;
  };

  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 {
    settings::Config game;
    settings::Config enemies;
    settings::Config items;
    settings::Config tiles;
    settings::Config devices;
    settings::Config scenes;
  };

  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 Sprite {
    string name;
    float scale;
  };

  struct AnimatedScene {
    std::string background;
    std::vector<std::string> layout;
    json actors;
    json fixtures;
    json buttons;
  };

  struct Storyboard {
    std::string image;
    std::string audio;
    std::vector<std::string> layout;
    std::vector<std::array<std::string, 4>> beats;
  };

  struct Curative {
    int hp = 10;
  };

  struct Combat {
    int max_hp=1;
    int ap_delta=1;
    int max_ap=1;
    int damage=1;
    std::unordered_map<std::string, int> body_parts{
        {"head", 10},
        {"chest", 10},
        {"stomach", 10},
        {"right_arm", 10},
        {"left_arm", 10},
        {"right_leg", 10},
        {"left_leg", 10},
    };

    std::array<std::string, 7> part_names{
        "head", "chest", "stomach",
        "right_arm", "left_arm",
        "right_leg", "left_leg"};

    // everyone starts at 0 but ap_delta is added each round
    int ap = 0;

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

    bool less_than(int level);
    int attack(Combat &target);
    void take_damage(int my_dmg);
    bool is_dead();
    bool almost_dead();
    bool can_heal();
    void apply_healing(Curative& cure);
  };

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

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

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

  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(AnimatedScene, background, layout, actors, fixtures, buttons);
  ENROLL_COMPONENT(Sprite, name, 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, max_hp, ap_delta, max_ap, damage, body_parts);
  ENROLL_COMPONENT(Device, config, events);
  ENROLL_COMPONENT(Storyboard, image, audio, layout, beats);
  ENROLL_COMPONENT(Sound, attack, death);
  ENROLL_COMPONENT(Collision, has);

  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);

}
