#include "utility/resourcemanager.h"
#include "gameplay/weapons/weapons.h"
#include "graphics/animation.h"
#include "graphics/background.h"
#include "graphics/shader.h"
#include "graphics/sprite.h"
#include "util.h"
#include "utility/script.h"

SpriteAtlas *ResourceManager::loadSpriteAtlas(const std::string &path,
                                              float frameSize,
                                              bool isDirectional) {
  auto iterator = sprites.find(path);
  if (iterator != sprites.end())
    return static_cast<SpriteAtlas *>(iterator->second.get());
  auto sprite =
      std::make_unique<SpriteAtlas>(path.c_str(), frameSize, isDirectional);
  sprites[path] = std::move(sprite);
  SpriteAtlas &l = static_cast<SpriteAtlas &>(*sprites[path].get());
  return static_cast<SpriteAtlas *>(sprites[path].get());
}

Sprite *ResourceManager::loadSpriteStatic(const std::string &path) {
  auto iterator = sprites.find(path);
  if (iterator != sprites.end())
    return iterator->second.get();
  auto sprite = std::make_unique<SpriteStatic>(path.c_str());
  if (!sprite->loaded())
    return nullptr;
  sprites[path] = std::move(sprite);
  return sprites[path].get();
}

const TileSetData *ResourceManager::loadTileSet(const std::string &name) {
  return xmlLoader->getTileSetData(name);
}

const unsigned ResourceManager::loadShader(const std::string &name,
                                           const std::string &vertexPath,
                                           const std::string &fragPath) {
  auto iterator = shaders.find(name);
  if (iterator != shaders.end())
    return iterator->second->ID;
  auto shader = std::make_unique<Shader>(vertexPath.c_str(), fragPath.c_str());
  if (!shader->isValid()) {
    return shaders["__fallback__"]->ID;
  }
  unsigned id = shader->ID;
  shaderIDs[shader->ID] = shader.get();
  shaders[name] = std::move(shader);
  return id;
}

Shader *ResourceManager::getShaderByID(unsigned int ID) {
  auto iterator = shaderIDs.find(ID);
  return (iterator != shaderIDs.end() ? iterator->second
                                      : shaders["__fallback__"].get());
}

Background *ResourceManager::loadBackground(const std::string &path) {
  auto iterator = backgrounds.find(path);
  if (iterator != backgrounds.end())
    return iterator->second.get();
  auto background = std::make_unique<Background>(path);
  backgrounds[path] = std::move(background);
  return backgrounds[path].get();
}

// We attach our script after we create our weapon because we are passing a
// reference to the weapon into the script and we don't want to pass an
// incomplete reference to our script.
std::unique_ptr<Weapon>
ResourceManager::loadWeapon(const std::string &id,
                            const unsigned weaponShaderID,
                            const unsigned bulletShaderID) {
  const WeaponData *data = xmlLoader->getWeaponData(id);
  if (!data) {
    LOG(ERROR, "Could not load weapon id '{}', falling back to pistol", id);
    data = xmlLoader->getWeaponData(
        "gun/pistol"); // using this as a fallback for now
  }
  auto weapon =
      std::make_unique<Weapon>(data, weaponShaderID, bulletShaderID, this);
  if (!data->script.empty())
    weapon->attachScript(loadScript<WeaponScript>(data->script));
  return std::move(weapon);
}

const unsigned ResourceManager::loadSoundEffect(const std::string &id) {
  auto iterator = sounds.find(id);
  if (iterator != sounds.end())
    return iterator->second->getBuffer();
  auto soundData = xmlLoader->getSoundData(id);
  if (!soundData) {
    soundData = xmlLoader->getSoundData(UTIL::get_generic(id));
    LOG(WARN, "Could not load sound id: '{}' trying generic", id);
    if (!soundData)
      return 0;
  }
  auto sound = std::make_unique<SoundEffect>(soundData->path);
  sounds[id] = std::move(sound);
  return sounds[id]->getBuffer();
}

const SceneData *ResourceManager::loadScene(const std::string &id) {
  return xmlLoader->getSceneData(id);
}

const std::vector<AnimationData*> ResourceManager::loadAnimationSet(const std::string &prefix) {
	return xmlLoader->getAnimationSet(prefix);
}

void ResourceManager::clearResources() {
  sprites.clear();
  shaders.clear();
  shaderIDs.clear();
  tileSets.clear();
}

ResourceManager::~ResourceManager() {
  clearResources();
  xmlLoader.reset();
}

bool ResourceManager::loadLuaString(ScriptData *scriptData) {
  std::filesystem::path file(scriptData->fileName);
  if (file.empty() || !file.has_extension())
    return false;
  try {

    std::ifstream fileStream(file);
    std::ostringstream sstr;
    sstr << fileStream.rdbuf();
    scriptData->script = sstr.str().c_str();
    fileStream.close();

  } catch (std::exception &e) {
    ERROR_LOG("Failed to read script file: '{}' - '{}'", scriptData->fileName,
              e.what());
  }
  return true;
}
