#include "asset.h"
#include <algorithm> // Para find_if, max
#include <fstream>   // Para basic_ostream, operator<<, basic_ifstream, endl
#include <iostream>  // Para cout
#include <string>    // Para allocator, char_traits, string, operator+, oper...
#include "utils.h"   // Para getFileName, printWithDots

// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Asset *Asset::asset_ = nullptr;

// [SINGLETON] Crearemos el objeto asset con esta función estática
void Asset::init(const std::string &executable_path)
{
    Asset::asset_ = new Asset(executable_path);
}

// [SINGLETON] Destruiremos el objeto asset con esta función estática
void Asset::destroy()
{
    delete Asset::asset_;
}

// [SINGLETON] Con este método obtenemos el objeto asset y podemos trabajar con él
Asset *Asset::get()
{
    return Asset::asset_;
}

// Añade un elemento a la lista
void Asset::add(const std::string &file, AssetType type, bool required, bool absolute)
{
    file_list_.emplace_back(absolute ? file : executable_path_ + file, type, required);
    longest_name_ = std::max(longest_name_, static_cast<int>(file_list_.back().file.size()));
}

// Devuelve la ruta completa a un fichero a partir de una cadena
std::string Asset::get(const std::string &text) const
{
    auto it = std::find_if(file_list_.begin(), file_list_.end(),
                           [&text](const auto &f)
                           {
                               return getFileName(f.file) == text;
                           });

    if (it != file_list_.end())
    {
        return it->file;
    }
    else
    {
        std::cout << "Warning: file " << text << " not found" << std::endl;
        return "";
    }
}

// Comprueba que existen todos los elementos
bool Asset::check() const
{
    bool success = true;

    std::cout << "\n** CHECKING FILES" << std::endl;

    // std::cout << "Executable path is:  " << executable_path_ << std::endl;
    // std::cout << "Sample filepath:  " << file_list_.back().file << std::endl;

    // Comprueba la lista de ficheros clasificandolos por tipo
    for (int type = 0; type < static_cast<int>(AssetType::MAX_ASSET_TYPE); ++type)
    {
        // Comprueba si hay ficheros de ese tipo
        bool any = false;

        for (const auto &f : file_list_)
        {
            if (f.required && f.type == static_cast<AssetType>(type))
            {
                any = true;
            }
        }

        // Si hay ficheros de ese tipo, comprueba si existen
        if (any)
        {
            std::cout << "\n>> " << getTypeName(static_cast<AssetType>(type)).c_str() << " FILES" << std::endl;

            for (const auto &f : file_list_)
            {
                if (f.required && f.type == static_cast<AssetType>(type))
                {
                    success &= checkFile(f.file);
                }
            }
            if (success)
                std::cout << "   All files are OK." << std::endl;
        }
    }

    // Resultado
    std::cout << (success ? "\n** CHECKING FILES COMPLETED.\n" : "\n** CHECKING FILES FAILED.\n") << std::endl;

    return success;
}

// Comprueba que existe un fichero
bool Asset::checkFile(const std::string &path) const
{
    std::ifstream file(path);
    bool success = file.good();
    file.close();

    if (!success)
        printWithDots("Checking file : ", getFileName(path), "[ ERROR ]");

    return success;
}

// Devuelve el nombre del tipo de recurso
std::string Asset::getTypeName(AssetType type) const
{
    switch (type)
    {
    case AssetType::DATA:
        return "DATA";
        break;

    case AssetType::BITMAP:
        return "BITMAP";
        break;

    case AssetType::ANIMATION:
        return "ANIMATION";
        break;

    case AssetType::MUSIC:
        return "MUSIC";
        break;

    case AssetType::SOUND:
        return "SOUND";
        break;

    case AssetType::FONT:
        return "FONT";
        break;

    case AssetType::ROOM:
        return "ROOM";
        break;

    case AssetType::TILEMAP:
        return "TILEMAP";
        break;

    default:
        return "ERROR";
        break;
    }
}

// Devuelve la lista de recursos de un tipo
std::vector<std::string> Asset::getListByType(AssetType type) const
{
    std::vector<std::string> list;

    for (auto f : file_list_)
    {
        if (f.type == type)
        {
            list.push_back(f.file);
        }
    }

    return list;
}