#include "common/jscore.h"
#include "stats.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>

// Constructor
Stats::Stats(std::string file, std::string buffer, options_t *options)
{
    this->options = options;
    bufferPath = buffer;
    filePath = file;
    bufferList.clear();
    list.clear();
    dictionary.clear();
}

// Destructor
Stats::~Stats()
{
    // Vuelca los datos del buffer en la lista de estadisticas
    updateListFromBuffer();

    // Calcula cual es la habitación con más muertes
    checkWorstNightmare();

    // Guarda las estadísticas
    saveToServer();
    saveToFile(bufferPath, bufferList);
    saveToFile(filePath, list);

    bufferList.clear();
    list.clear();
    dictionary.clear();
}

// Inicializador
// Se debe llamar a este procediiento una vez se haya creado el diccionario numero-nombre
void Stats::init()
{
    loadFromFile(bufferPath, bufferList);
    loadFromFile(filePath, list);
    if (options->online.enabled)
    {
        loadFromServer();
    }

    // Vuelca los datos del buffer en la lista de estadisticas
    updateListFromBuffer();
}

// Añade una muerte a las estadisticas
void Stats::addDeath(std::string name)
{
    // Normaliza el nombre
    // std::replace(name.begin(), name.end(), ' ', '_');

    // Primero busca si ya hay una entrada con ese nombre
    const int index = findByName(name, bufferList);
    if (index != -1)
    {
        bufferList[index].died++;
    }

    // En caso contrario crea la entrada
    else
    {
        stats_t item;
        item.name = name;
        item.visited = 0;
        item.died = 1;
        bufferList.push_back(item);
    }
}

// Añade una visita a las estadisticas
void Stats::addVisit(std::string name)
{
    // Normaliza el nombre
    // std::replace(name.begin(), name.end(), ' ', '_');

    // Primero busca si ya hay una entrada con ese nombre
    const int index = findByName(name, bufferList);
    if (index != -1)
    {
        bufferList[index].visited++;
    }

    // En caso contrario crea la entrada
    else
    {
        stats_t item;
        item.name = name;
        item.visited = 1;
        item.died = 0;
        bufferList.push_back(item);
    }
}

// Busca una entrada en la lista por nombre
int Stats::findByName(std::string name, std::vector<stats_t> &list)
{
    int i = 0;

    for (auto l : list)
    {
        if (l.name == name)
        {
            return i;
        }
        i++;
    }

    return -1;
}

// Carga las estadisticas desde un fichero
bool Stats::loadFromFile(std::string filePath, std::vector<stats_t> &list)
{
    list.clear();

    // Indicador de éxito en la carga
    bool success = true;

    // Variables para manejar el fichero
    std::string line;
    std::ifstream file(filePath);

    // Si el fichero se puede abrir
    if (file.good())
    {
        // Procesa el fichero linea a linea
        while (std::getline(file, line))
        {
            // Comprueba que la linea no sea un comentario
            if (line.substr(0, 1) != "#")
            {
                stats_t stat;
                std::stringstream ss(line);
                std::string tmp;

                // Obtiene el nombre
                getline(ss, tmp, ';');
                stat.name = tmp;

                // Obtiene las visitas
                getline(ss, tmp, ';');
                stat.visited = std::stoi(tmp);

                // Obtiene las muertes
                getline(ss, tmp, ';');
                stat.died = std::stoi(tmp);

                list.push_back(stat);
            }
        }

        // Cierra el fichero
        file.close();
    }

    // El fichero no existe
    else
    { // Crea el fichero con los valores por defecto
        saveToFile(filePath, list);
    }

    return success;
}

// Carga las estadisticas desde un servidor
void Stats::loadFromServer()
{
    // Limpia los datos del servidor
    // eraseServerData();

    list.clear();

    std::string data;
    if (options->online.enabled)
    {
        data = jscore::getUserData(options->online.gameID, options->online.jailerID);
    }

    std::stringstream ss(data);
    std::string tmp;

    int count = 0;

    for (int i = 0; i < (int)data.size(); ++i)
    {
        if (data[i] == ';')
        {
            count++;
        }
    }

    while (count > 0)
    {
        stats_t stat;

        // Obtiene el nombre
        getline(ss, tmp, ';');
        stat.name = numberToName(tmp);

        // Obtiene las visitas
        getline(ss, tmp, ';');
        stat.visited = std::stoi(tmp);

        // Obtiene las muertes
        getline(ss, tmp, ';');
        stat.died = std::stoi(tmp);

        list.push_back(stat);
        count = count - 3;
    }
}

// Guarda las estadisticas en un fichero
void Stats::saveToFile(std::string filePath, std::vector<stats_t> &list)
{
    // Crea y abre el fichero de texto
    std::ofstream file(filePath);

    // Escribe en el fichero
    file << "# ROOM NAME;VISITS;DEATHS" << std::endl;
    for (auto item : list)
    {
        file << item.name << ";" << item.visited << ";" << item.died << std::endl;
    }

    // Cierra el fichero
    file.close();
}

// Guarda las estadisticas en un servidor
void Stats::saveToServer()
{
    std::string data = "";
    if (options->online.enabled)
    {
        for (auto item : list)
        {
            data = data + nameToNumber(item.name) + ";" + std::to_string(item.visited) + ";" + std::to_string(item.died) + ";";
        }
        jscore::setUserData(options->online.gameID, options->online.jailerID, data);
    }
}

// Calcula cual es la habitación con más muertes
void Stats::checkWorstNightmare()
{
    int deaths = 0;
    for (auto item : list)
    {
        if (item.died > deaths)
        {
            deaths = item.died;
            options->stats.worstNightmare = item.name;
        }
    }
}

// Añade una entrada al diccionario
void Stats::addDictionary(std::string number, std::string name)
{
    dictionary.push_back({number, name});
}

// Obtiene el nombre de una habitación a partir del número
std::string Stats::numberToName(std::string number)
{
    for (auto l : dictionary)
    {
        if (l.number == number)
        {
            return l.name;
        }
    }
    return "";
}

// Obtiene el número de una habitación a partir del nombre
std::string Stats::nameToNumber(std::string name)
{
    for (auto l : dictionary)
    {
        if (l.name == name)
        {
            return l.number;
        }
    }
    return "";
}

// Vuelca los datos del buffer en la lista de estadisticas
void Stats::updateListFromBuffer()
{
    // Actualiza list desde bufferList
    for (auto buffer : bufferList)
    {
        int index = findByName(buffer.name, list);

        if (index != -1)
        { // Encontrado. Aumenta sus estadisticas
            list[index].visited += buffer.visited;
            list[index].died += buffer.died;
        }
        else
        { // En caso contrario crea la entrada
            stats_t item;
            item.name = buffer.name;
            item.visited = buffer.visited;
            item.died = buffer.died;
            list.push_back(item);
        }
    }

    // Sube los datos al servidor
    if (options->online.enabled)
    {
        saveToServer();
        bufferList.clear();
    }

    saveToFile(bufferPath, bufferList);
    saveToFile(filePath, list);
}

// Limpia los datos del servidor
void Stats::eraseServerData()
{
    jscore::setUserData(options->online.gameID, options->online.jailerID, "");
}