#include "textures.hpp"
#include <SFML/Graphics/Image.hpp>
#include "dbc.hpp"
#include <fmt/core.h>
#include "config.hpp"
#include "constants.hpp"
#include <memory>

namespace textures {
  using std::shared_ptr, std::make_shared, nlohmann::json, std::string;

  static TextureManager TMGR;
  static bool initialized = false;

  void load_sprite_textures(SpriteTextureMap &mapping, json &config, bool smooth) {
    for(auto& [name, settings] : config.items()) {
      auto texture = make_shared<sf::Texture>(settings["path"]);

      texture->setSmooth(smooth);
      auto sprite = make_shared<sf::Sprite>(*texture);

      int width = settings["frame_width"];
      int height = settings["frame_height"];
      dbc::check(width % 2 == 0,
          fmt::format("sprite {} has invalid frame size {}", name, width));

      sf::Vector2i frame_size{width, height};

      sprite->setTextureRect({{0,0}, frame_size});

      dbc::check(!mapping.contains(name),
          fmt::format("duplicate sprite/icon name {}", (string)name));
      mapping.try_emplace(name, sprite, texture, frame_size);
    }
  }

  void load_sprites() {
    Config sprites("assets/config.json");
    bool smooth = sprites["graphics"]["smooth_textures"];

    load_sprite_textures(TMGR.sprite_textures, sprites["sprites"], smooth);

    Config icons("assets/icons.json");
    load_sprite_textures(TMGR.icon_textures, icons.json(), smooth);
  }

  inline void resize_shit(size_t size) {
    TMGR.surfaces.resize(size);
    TMGR.ceilings.resize(size);
    TMGR.map_tile_set.resize(size);
    TMGR.ambient_light.resize(size);
  }

  void load_tiles() {
    Config assets("assets/tiles.json");
    auto &tiles = assets.json();

    resize_shit(tiles.size());

    for(auto &el : tiles.items()) {
      auto &config = el.value();
      const string& texture_fname = config["texture"];
      size_t surface_i = config["id"];

      dbc::check(!TMGR.name_to_id.contains(el.key()),
          fmt::format("duplicate key in textures {}",
            (string)el.key()));

      TMGR.name_to_id.insert_or_assign(el.key(), surface_i);

      if(surface_i >= tiles.size()) {
        resize_shit(surface_i + 1);
      }

      TMGR.map_tile_set[surface_i] = config["display"];
      TMGR.ambient_light[surface_i] = config["light"];
      TMGR.surfaces[surface_i] = load_image(texture_fname);

      // NOTE: ceilings defaults to 0 which is floor texture so only need to update
      if(config.contains("ceiling")) {
        const string& name = config["ceiling"];

        dbc::check(tiles.contains(name), fmt::format("invalid ceiling name {} in tile config {}", name, (string)el.key()));

        auto& ceiling = tiles[name];
        TMGR.ceilings[surface_i] = ceiling["id"];
      }
    }
  }

  void load_map_tiles() {
    Config config("./assets/map_tiles.json");
    json& tiles = config.json();

    for(auto tile : tiles) {
      sf::Vector2i coords{tile["x"], tile["y"]};
      dbc::check(coords.x % ICONGEN_MAP_TILE_DIM == 0, "x coordinates wrong in map");
      dbc::check(coords.y % ICONGEN_MAP_TILE_DIM == 0, "y coordinates wrong in map");

      sf::IntRect square{coords, {ICONGEN_MAP_TILE_DIM, ICONGEN_MAP_TILE_DIM}};
      sf::Sprite sprite{TMGR.map_sprite_sheet, square};
      wchar_t display = tile["display"];

      dbc::check(!TMGR.map_sprites.contains(display),
          fmt::format("duplicate tile display {} in map_tiles.json", int(display)));

      TMGR.map_sprites.try_emplace(display, sprite);
    }
  }

  void init() {
    if(!initialized) {
      load_tiles();
      load_sprites();
      load_map_tiles();
      initialized = true;
    }
  }

  SpriteTexture& get(const string& name, SpriteTextureMap& mapping) {
    dbc::check(initialized, "you forgot to call textures::init()");
    dbc::check(mapping.contains(name),
        fmt::format("!!!!! textures do not contain {} sprite", name));

    auto& result = mapping.at(name);

    dbc::check(result.sprite != nullptr,
        fmt::format("bad sprite from textures::get named {}", name));
    dbc::check(result.texture != nullptr,
        fmt::format("bad texture from textures::get named {}", name));

    return result;
  }

  SpriteTexture get_sprite(const string& name) {
    return get(name, TMGR.sprite_textures);
  }

  SpriteTexture get_icon(const string& name) {
    return get(name, TMGR.icon_textures);
  }

  sf::Image load_image(const string& filename) {
    sf::Image texture;
    bool good = texture.loadFromFile(filename);
    dbc::check(good, fmt::format("failed to load {}", filename));
    return texture;
  }

  std::vector<int>& get_ambient_light() {
    return TMGR.ambient_light;
  }

  std::vector<wchar_t>& get_map_tile_set() {
    return TMGR.map_tile_set;
  }

  const uint32_t* get_surface(size_t num) {
    return (const uint32_t *)TMGR.surfaces[num].getPixelsPtr();
  }

  sf::Image& get_surface_img(size_t num) {
    return TMGR.surfaces[num];
  }

  const uint32_t* get_ceiling(size_t num) {
    size_t ceiling_num = TMGR.ceilings[num];
    return (const uint32_t *)TMGR.surfaces[ceiling_num].getPixelsPtr();
  }

  size_t get_id(const string& name) {
    dbc::check(TMGR.name_to_id.contains(name),
        fmt::format("there is no texture named {} in tiles.json", name));
    return TMGR.name_to_id.at(name);
  }

  sf::Sprite& get_map_sprite(wchar_t display) {
    dbc::check(TMGR.map_sprites.contains(display),
        fmt::format("map_sprites.json doesn't have {} sprite", int(display)));

    return TMGR.map_sprites.at(display);
  }
};
