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

namespace shaders {
  using std::shared_ptr, std::make_shared;

  static ShaderManager SMGR;
  static bool INITIALIZED = false;
  static int VERSION = 0;

  inline void configure_shader_defaults(std::shared_ptr<sf::Shader> ptr) {
    ptr->setUniform("source", sf::Shader::CurrentTexture);
  }

  bool load_shader(std::string name, nlohmann::json& settings) {
    std::string file_name = settings["file_name"];
    auto ptr = std::make_shared<sf::Shader>();
    bool good = ptr->loadFromFile(file_name, sf::Shader::Type::Fragment);

    if(good) {
      configure_shader_defaults(ptr);
      SMGR.shaders.try_emplace(name, name, file_name, ptr);
    }
    return good;
  }

  void init() {
    if(!INITIALIZED) {
      dbc::check(sf::Shader::isAvailable(), "no shaders?!");
      INITIALIZED = true;
      Config config("assets/shaders.json");
      bool good = load_shader("ERROR", config["ERROR"]);
      dbc::check(good, "Failed to load ERROR shader. Look in assets/shaders.json");

      for(auto& [name, settings] : config.json().items()) {
        if(name == "ERROR") continue;

        dbc::check(!SMGR.shaders.contains(name),
            fmt::format("shader name '{}' duplicated in assets/shaders.json", name));
        good = load_shader(name, settings);

        if(!good) {
          dbc::log(fmt::format("failed to load shader {}", name));
          SMGR.shaders.insert_or_assign(name, SMGR.shaders.at("ERROR"));
        }
      }
    }
  }

  std::shared_ptr<sf::Shader> get(const std::string& name) {
    dbc::check(INITIALIZED, "you forgot to shaders::init()");
    dbc::check(SMGR.shaders.contains(name),
        fmt::format("shader name '{}' not in assets/shaders.json", name));
    auto& rec = SMGR.shaders.at(name);
    return rec.ptr;
  }

  int reload() {
    VERSION++;
    INITIALIZED = false;
    SMGR.shaders.clear();
    init();
    return VERSION;
  }

  bool updated(int my_version) {
    return my_version != VERSION;
  }

  int version() {
    return VERSION;
  }
};