#include "core/rendering/animatedsprite.h"

#include <fstream>   // for basic_ostream, operator<<, basic_istream, basic...
#include <iostream>  // for cout
#include <sstream>   // for basic_stringstream

#include "core/rendering/texture.h"          // for Texture
#include "core/resources/resource_helper.h"  // for loadFile (pack + filesystem fallback)

// Parser compartido: lee un istream con el formato .ani
static auto parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) -> AnimatedSpriteData {
    AnimatedSpriteData as;
    as.texture = texture;
    int framesPerRow = 0;
    int frameWidth = 0;
    int frameHeight = 0;
    int maxTiles = 0;
    std::string line;

    if (verbose) {
        std::cout << "Animation loaded: " << filename << '\n';
    }
    // Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que
    // line == "[animation]" no faci match i el parser entri en bucle
    // infinit / no carregui cap animació.
    auto strip_cr = [](std::string &s) {
        if (!s.empty() && s.back() == '\r') {
            s.pop_back();
        }
    };

    while (std::getline(file, line)) {
        strip_cr(line);
        if (line == "[animation]") {
            Animation buffer;
            buffer.speed = 0;
            buffer.loop = -1;
            buffer.counter = 0;
            buffer.current_frame = 0;
            buffer.completed = false;

            do {
                if (!std::getline(file, line)) {
                    break;
                }
                strip_cr(line);
                int pos = line.find('=');
                if (pos != (int)std::string::npos) {
                    if (line.substr(0, pos) == "name") {
                        buffer.name = line.substr(pos + 1, line.length());
                    } else if (line.substr(0, pos) == "speed") {
                        buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
                    } else if (line.substr(0, pos) == "loop") {
                        buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
                    } else if (line.substr(0, pos) == "frames") {
                        std::stringstream ss(line.substr(pos + 1, line.length()));
                        std::string tmp;
                        SDL_Rect rect = {0, 0, frameWidth, frameHeight};
                        while (getline(ss, tmp, ',')) {
                            const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
                            rect.x = (numTile % framesPerRow) * frameWidth;
                            rect.y = (numTile / framesPerRow) * frameHeight;
                            buffer.frames.push_back(rect);
                        }
                    } else {
                        std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << '\n';
                    }
                }
            } while (line != "[/animation]");

            as.animations.push_back(buffer);
        } else {
            int pos = line.find('=');
            if (pos != (int)std::string::npos) {
                if (line.substr(0, pos) == "framesPerRow") {
                    framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
                } else if (line.substr(0, pos) == "frameWidth") {
                    frameWidth = std::stoi(line.substr(pos + 1, line.length()));
                } else if (line.substr(0, pos) == "frameHeight") {
                    frameHeight = std::stoi(line.substr(pos + 1, line.length()));
                } else {
                    std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << '\n';
                }

                if (framesPerRow == 0 && frameWidth > 0) {
                    framesPerRow = texture->getWidth() / frameWidth;
                }
                if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
                    const int w = texture->getWidth() / frameWidth;
                    const int h = texture->getHeight() / frameHeight;
                    maxTiles = w * h;
                }
            }
        }
    }

    return as;
}

// Carga la animación desde un fichero (vía ResourceHelper: pack si està inicialitzat, filesystem si no)
auto loadAnimationFromFile(Texture *texture, const std::string &file_path, bool verbose) -> AnimatedSpriteData {
    const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
    auto bytes = ResourceHelper::loadFile(file_path);
    if (bytes.empty()) {
        if (verbose) {
            std::cout << "Warning: Unable to open " << filename.c_str() << " file" << '\n';
        }
        AnimatedSpriteData as;
        as.texture = texture;
        return as;
    }
    return loadAnimationFromMemory(texture, bytes, filename, verbose);
}

// Carga la animación desde bytes en memoria
auto loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &name_for_logs, bool verbose) -> AnimatedSpriteData {
    if (bytes.empty()) {
        AnimatedSpriteData as;
        as.texture = texture;
        return as;
    }
    std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
    std::stringstream ss(content);
    return parseAnimationStream(ss, texture, name_for_logs, verbose);
}

// Constructor
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, const std::string &file, std::vector<std::string> *buffer)
    : current_animation_(0) {
    // Copia los punteros
    setTexture(texture);
    setRenderer(renderer);

    // Carga las animaciones
    if (!file.empty()) {
        AnimatedSpriteData as = loadAnimationFromFile(texture, file);

        // Copia los datos de las animaciones
        animation_.insert(animation_.end(), as.animations.begin(), as.animations.end());
    }

    else if (buffer != nullptr) {
        loadFromVector(buffer);
    }
}

// Constructor
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, AnimatedSpriteData *data)
    : current_animation_(0) {
    // Copia los punteros
    setTexture(data->texture);
    setRenderer(renderer);

    // Copia los datos de las animaciones
    this->animation_.insert(this->animation_.end(), data->animations.begin(), data->animations.end());
}

// Destructor
AnimatedSprite::~AnimatedSprite() {
    for (auto &a : animation_) {
        a.frames.clear();
    }
    animation_.clear();
}

// Obtiene el indice de la animación a partir del nombre
auto AnimatedSprite::getIndex(const std::string &name) -> int {
    int index = -1;

    for (const auto &a : animation_) {
        index++;
        if (a.name == name) {
            return index;
        }
    }

    std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << '\n';

    return -1;
}

// Calcula el frame correspondiente a la animación
void AnimatedSprite::animate() {
    if (!enabled_ || animation_[current_animation_].speed == 0) {
        return;
    }

    // Calcula el frame actual a partir del contador
    animation_[current_animation_].current_frame = animation_[current_animation_].counter / animation_[current_animation_].speed;

    // Si alcanza el final de la animación, reinicia el contador de la animación
    // en función de la variable loop y coloca el nuevo frame
    if (animation_[current_animation_].current_frame >= (int)animation_[current_animation_].frames.size()) {
        if (animation_[current_animation_].loop == -1) {  // Si no hay loop, deja el último frame
            animation_[current_animation_].current_frame = animation_[current_animation_].frames.size();
            animation_[current_animation_].completed = true;
        } else {  // Si hay loop, vuelve al frame indicado
            animation_[current_animation_].counter = 0;
            animation_[current_animation_].current_frame = animation_[current_animation_].loop;
        }
    }
    // En caso contrario
    else {
        // Escoge el frame correspondiente de la animación
        setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]);

        // Incrementa el contador de la animacion
        animation_[current_animation_].counter++;
    }
}

// Obtiene el numero de frames de la animación actual
auto AnimatedSprite::getNumFrames() -> int {
    return (int)animation_[current_animation_].frames.size();
}

// Establece el frame actual de la animación
void AnimatedSprite::setCurrentFrame(int num) {
    // Descarta valores fuera de rango
    if (num >= (int)animation_[current_animation_].frames.size()) {
        num = 0;
    }

    // Cambia el valor de la variable
    animation_[current_animation_].current_frame = num;
    animation_[current_animation_].counter = 0;

    // Escoge el frame correspondiente de la animación
    setSpriteClip(animation_[current_animation_].frames[animation_[current_animation_].current_frame]);
}

// Establece el valor del contador
void AnimatedSprite::setAnimationCounter(const std::string &name, int num) {
    animation_[getIndex(name)].counter = num;
}

// Establece la velocidad de una animación
void AnimatedSprite::setAnimationSpeed(const std::string &name, int speed) {
    animation_[getIndex(name)].counter = speed;
}

// Establece la velocidad de una animación
void AnimatedSprite::setAnimationSpeed(int index, int speed) {
    animation_[index].counter = speed;
}

// Establece si la animación se reproduce en bucle
void AnimatedSprite::setAnimationLoop(const std::string &name, int loop) {
    animation_[getIndex(name)].loop = loop;
}

// Establece si la animación se reproduce en bucle
void AnimatedSprite::setAnimationLoop(int index, int loop) {
    animation_[index].loop = loop;
}

// Establece el valor de la variable
void AnimatedSprite::setAnimationCompleted(const std::string &name, bool value) {
    animation_[getIndex(name)].completed = value;
}

// OLD - Establece el valor de la variable
void AnimatedSprite::setAnimationCompleted(int index, bool value) {
    animation_[index].completed = value;
}

// Comprueba si ha terminado la animación
auto AnimatedSprite::animationIsCompleted() -> bool {
    return animation_[current_animation_].completed;
}

// Devuelve el rectangulo de una animación y frame concreto
auto AnimatedSprite::getAnimationClip(const std::string &name, Uint8 index) -> SDL_Rect {
    return animation_[getIndex(name)].frames[index];
}

// Devuelve el rectangulo de una animación y frame concreto
auto AnimatedSprite::getAnimationClip(int index_a, Uint8 index_f) -> SDL_Rect {
    return animation_[index_a].frames[index_f];
}

// Carga la animación desde un vector
auto AnimatedSprite::loadFromVector(std::vector<std::string> *source) -> bool {
    // Inicializa variables
    int framesPerRow = 0;
    int frameWidth = 0;
    int frameHeight = 0;
    int maxTiles = 0;

    // Indicador de éxito en el proceso
    bool success = true;
    std::string line;

    // Recorre todo el vector
    int index = 0;
    while (index < (int)source->size()) {
        // Lee desde el vector
        line = source->at(index);

        // Si la linea contiene el texto [animation_] se realiza el proceso de carga de una animación
        if (line == "[animation]") {
            Animation buffer;
            buffer.speed = 0;
            buffer.loop = -1;
            buffer.counter = 0;
            buffer.current_frame = 0;
            buffer.completed = false;

            do {
                // Aumenta el indice para leer la siguiente linea
                index++;
                line = source->at(index);

                // Encuentra la posición del caracter '='
                int pos = line.find('=');

                // Procesa las dos subcadenas
                if (pos != (int)std::string::npos) {
                    if (line.substr(0, pos) == "name") {
                        buffer.name = line.substr(pos + 1, line.length());
                    }

                    else if (line.substr(0, pos) == "speed") {
                        buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
                    }

                    else if (line.substr(0, pos) == "loop") {
                        buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
                    }

                    else if (line.substr(0, pos) == "frames") {
                        // Se introducen los valores separados por comas en un vector
                        std::stringstream ss(line.substr(pos + 1, line.length()));
                        std::string tmp;
                        SDL_Rect rect = {0, 0, frameWidth, frameHeight};
                        while (getline(ss, tmp, ',')) {
                            // Comprueba que el tile no sea mayor que el maximo indice permitido
                            const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
                            rect.x = (numTile % framesPerRow) * frameWidth;
                            rect.y = (numTile / framesPerRow) * frameHeight;
                            buffer.frames.push_back(rect);
                        }
                    }

                    else {
                        std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << '\n';
                        success = false;
                    }
                }
            } while (line != "[/animation]");

            // Añade la animación al vector de animaciones
            animation_.push_back(buffer);
        }

        // En caso contrario se parsea el fichero para buscar las variables y los valores
        else {
            // Encuentra la posición del caracter '='
            int pos = line.find('=');

            // Procesa las dos subcadenas
            if (pos != (int)std::string::npos) {
                if (line.substr(0, pos) == "framesPerRow") {
                    framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
                }

                else if (line.substr(0, pos) == "frameWidth") {
                    frameWidth = std::stoi(line.substr(pos + 1, line.length()));
                }

                else if (line.substr(0, pos) == "frameHeight") {
                    frameHeight = std::stoi(line.substr(pos + 1, line.length()));
                }

                else {
                    std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << '\n';
                    success = false;
                }

                // Normaliza valores
                if (framesPerRow == 0 && frameWidth > 0) {
                    framesPerRow = texture_->getWidth() / frameWidth;
                }

                if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
                    const int w = texture_->getWidth() / frameWidth;
                    const int h = texture_->getHeight() / frameHeight;
                    maxTiles = w * h;
                }
            }
        }

        // Una vez procesada la linea, aumenta el indice para pasar a la siguiente
        index++;
    }

    // Pone un valor por defecto
    setRect({0, 0, frameWidth, frameHeight});

    return success;
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(const std::string &name) {
    const int newAnimation = getIndex(name);
    if (current_animation_ != newAnimation) {
        current_animation_ = newAnimation;
        animation_[current_animation_].current_frame = 0;
        animation_[current_animation_].counter = 0;
        animation_[current_animation_].completed = false;
    }
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(int index) {
    const int newAnimation = index;
    if (current_animation_ != newAnimation) {
        current_animation_ = newAnimation;
        animation_[current_animation_].current_frame = 0;
        animation_[current_animation_].counter = 0;
        animation_[current_animation_].completed = false;
    }
}

// Actualiza las variables del objeto
void AnimatedSprite::update() {
    animate();
    MovingSprite::update();
}

// Establece el rectangulo para un frame de una animación
void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h) {
    animation_[index_animation].frames.push_back({x, y, w, h});
}

// OLD - Establece el contador para todas las animaciones
void AnimatedSprite::setAnimationCounter(int value) {
    for (auto &a : animation_) {
        a.counter = value;
    }
}

// Reinicia la animación
void AnimatedSprite::resetAnimation() {
    animation_[current_animation_].current_frame = 0;
    animation_[current_animation_].counter = 0;
    animation_[current_animation_].completed = false;
}