#include "core/rendering/surface_animated_sprite.hpp"

#include <cstddef>    // Para size_t
#include <fstream>    // Para basic_ostream, basic_istream, operator<<, basic...
#include <iostream>   // Para cout, cerr
#include <sstream>    // Para basic_stringstream
#include <stdexcept>  // Para runtime_error
#include <utility>

#include "core/rendering/surface.hpp"  // Para Surface
#include "utils/utils.hpp"             // Para printWithDots

// Carga las animaciones en un vector(Animations) desde un fichero
auto loadAnimationsFromFile(const std::string& file_path) -> Animations {
    std::ifstream file(file_path);
    if (!file) {
        std::cerr << "Error: Fichero no encontrado " << file_path << '\n';
        throw std::runtime_error("Fichero no encontrado: " + file_path);
    }

    printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");

    std::vector<std::string> buffer;
    std::string line;
    while (std::getline(file, line)) {
        if (!line.empty()) {
            buffer.push_back(line);
        }
    }

    return buffer;
}

// Constructor
SurfaceAnimatedSprite::SurfaceAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path)
    : SurfaceMovingSprite(std::move(surface)) {
    // Carga las animaciones
    if (!file_path.empty()) {
        Animations v = loadAnimationsFromFile(file_path);
        setAnimations(v);
    }
}

// Constructor
SurfaceAnimatedSprite::SurfaceAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations)
    : SurfaceMovingSprite(std::move(surface)) {
    if (!animations.empty()) {
        setAnimations(animations);
    }
}

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

    for (const auto& a : animations_) {
        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 (time-based)
void SurfaceAnimatedSprite::animate(float delta_time) {
    if (animations_[current_animation_].speed <= 0.0F) {
        return;
    }

    // Acumula el tiempo transcurrido
    animations_[current_animation_].accumulated_time += delta_time;

    // Calcula el frame actual a partir del tiempo acumulado
    const int TARGET_FRAME = static_cast<int>(
        animations_[current_animation_].accumulated_time /
        animations_[current_animation_].speed
    );

    // Si alcanza el final de la animación, maneja el loop
    if (TARGET_FRAME >= static_cast<int>(animations_[current_animation_].frames.size())) {
        if (animations_[current_animation_].loop == -1) {
            // Si no hay loop, congela en el último frame
            animations_[current_animation_].current_frame =
                static_cast<int>(animations_[current_animation_].frames.size()) - 1;
            animations_[current_animation_].completed = true;

            // Establece el clip del último frame
            if (animations_[current_animation_].current_frame >= 0) {
                setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
            }
        } else {
            // Si hay loop, vuelve al frame indicado
            animations_[current_animation_].accumulated_time =
                static_cast<float>(animations_[current_animation_].loop) *
                animations_[current_animation_].speed;
            animations_[current_animation_].current_frame = animations_[current_animation_].loop;

            // Establece el clip del frame de loop
            setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
        }
    } else {
        // Actualiza el frame actual
        animations_[current_animation_].current_frame = TARGET_FRAME;

        // Establece el clip del frame actual
        if (animations_[current_animation_].current_frame >= 0 &&
            animations_[current_animation_].current_frame <
            static_cast<int>(animations_[current_animation_].frames.size())) {
            setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
        }
    }
}

// Calcula el frame correspondiente a la animación (frame-based, deprecated)
void SurfaceAnimatedSprite::animate() {
    if (animations_[current_animation_].speed == 0.0F) {
        return;
    }

    // Calcula el frame actual a partir del contador (sistema antiguo)
    animations_[current_animation_].current_frame =
        static_cast<int>(animations_[current_animation_].counter / animations_[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 (animations_[current_animation_].current_frame >= static_cast<int>(animations_[current_animation_].frames.size())) {
        if (animations_[current_animation_].loop == -1) {  // Si no hay loop, deja el último frame
            animations_[current_animation_].current_frame =
                static_cast<int>(animations_[current_animation_].frames.size()) - 1;
            animations_[current_animation_].completed = true;
        } else {  // Si hay loop, vuelve al frame indicado
            animations_[current_animation_].counter = 0;
            animations_[current_animation_].current_frame = animations_[current_animation_].loop;
        }
    }
    // En caso contrario
    else {
        // Escoge el frame correspondiente de la animación
        setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);

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

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

// Establece la animacion actual
void SurfaceAnimatedSprite::setCurrentAnimation(const std::string& name) {
    const auto NEW_ANIMATION = getIndex(name);
    if (current_animation_ != NEW_ANIMATION) {
        current_animation_ = NEW_ANIMATION;
        animations_[current_animation_].current_frame = 0;
        animations_[current_animation_].accumulated_time = 0.0F;  // Time-based
        animations_[current_animation_].counter = 0;              // Frame-based (deprecated)
        animations_[current_animation_].completed = false;
        setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
    }
}

// Establece la animacion actual
void SurfaceAnimatedSprite::setCurrentAnimation(int index) {
    const auto NEW_ANIMATION = index;
    if (current_animation_ != NEW_ANIMATION) {
        current_animation_ = NEW_ANIMATION;
        animations_[current_animation_].current_frame = 0;
        animations_[current_animation_].accumulated_time = 0.0F;  // Time-based
        animations_[current_animation_].counter = 0;              // Frame-based (deprecated)
        animations_[current_animation_].completed = false;
        setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
    }
}

// Actualiza las variables del objeto (time-based)
void SurfaceAnimatedSprite::update(float delta_time) {
    animate(delta_time);
    SurfaceMovingSprite::update(delta_time);
}

// Actualiza las variables del objeto (frame-based, deprecated)
void SurfaceAnimatedSprite::update() {
    animate();
    SurfaceMovingSprite::update();
}

// Reinicia la animación
void SurfaceAnimatedSprite::resetAnimation() {
    animations_[current_animation_].current_frame = 0;
    animations_[current_animation_].accumulated_time = 0.0F;  // Time-based
    animations_[current_animation_].counter = 0;              // Frame-based (deprecated)
    animations_[current_animation_].completed = false;
}

// Helper: Parsea los parámetros de configuración globales (frame_width, frame_height)
auto parseGlobalParameter(const std::string& line, float& frame_width, float& frame_height) -> bool {
    size_t pos = line.find('=');
    if (pos == std::string::npos) {
        return false;
    }

    std::string key = line.substr(0, pos);
    int value = std::stoi(line.substr(pos + 1));

    if (key == "frame_width") {
        frame_width = value;
        return true;
    }
    if (key == "frame_height") {
        frame_height = value;
        return true;
    }

    std::cout << "Warning: unknown parameter " << key << '\n';
    return false;
}

// Helper: Parsea los frames de una animación desde una cadena separada por comas
void parseAnimationFrames(const std::string& value, AnimationData& animation,
                          float frame_width, float frame_height,
                          int frames_per_row, int max_tiles) {
    std::stringstream ss(value);
    std::string tmp;
    SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height};

    while (getline(ss, tmp, ',')) {
        const int NUM_TILE = std::stoi(tmp);
        if (NUM_TILE <= max_tiles) {
            rect.x = (NUM_TILE % frames_per_row) * frame_width;
            rect.y = (NUM_TILE / frames_per_row) * frame_height;
            animation.frames.emplace_back(rect);
        }
    }
}

// Helper: Parsea un parámetro de animación individual
auto parseAnimationParameter(const std::string& key, const std::string& value, AnimationData& animation, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> bool {
    if (key == "name") {
        animation.name = value;
        return true;
    }
    if (key == "speed") {
        // Soporta tanto float (segundos) como int (compatibilidad con sistema antiguo)
        animation.speed = std::stof(value);
        return true;
    }
    if (key == "loop") {
        animation.loop = std::stoi(value);
        return true;
    }
    if (key == "frames") {
        parseAnimationFrames(value, animation, frame_width, frame_height, frames_per_row, max_tiles);
        return true;
    }

    std::cout << "Warning: unknown parameter " << key << '\n';
    return false;
}

// Helper: Parsea una animación completa
auto parseAnimation(const Animations& animations, size_t& index, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> AnimationData {
    AnimationData animation;
    std::string line;

    do {
        index++;
        line = animations.at(index);
        size_t pos = line.find('=');

        if (pos != std::string::npos) {
            std::string key = line.substr(0, pos);
            std::string value = line.substr(pos + 1);
            parseAnimationParameter(key, value, animation, frame_width, frame_height,
                                   frames_per_row, max_tiles);
        }
    } while (line != "[/animation]");

    return animation;
}

// Carga la animación desde un vector de cadenas
void SurfaceAnimatedSprite::setAnimations(const Animations& animations) {
    float frame_width = 1.0F;
    float frame_height = 1.0F;
    int frames_per_row = 1;
    int max_tiles = 1;

    size_t index = 0;
    while (index < animations.size()) {
        const std::string& line = animations.at(index);

        // Parsea el fichero para buscar variables y valores
        if (line != "[animation]") {
            if (parseGlobalParameter(line, frame_width, frame_height)) {
                frames_per_row = surface_->getWidth() / frame_width;
                const int W = surface_->getWidth() / frame_width;
                const int H = surface_->getHeight() / frame_height;
                max_tiles = W * H;
            }
        }

        // Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
        if (line == "[animation]") {
            AnimationData animation = parseAnimation(animations, index, frame_width, frame_height,
                                                     frames_per_row, max_tiles);
            animations_.emplace_back(animation);
        }

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

    // Pone un valor por defecto
    setWidth(frame_width);
    setHeight(frame_height);
}

// Establece el frame actual de la animación
void SurfaceAnimatedSprite::setCurrentAnimationFrame(int num) {
    // Descarta valores fuera de rango
    if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
        num = 0;
    }

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

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