#include "core/rendering/sprite/animated_sprite.hpp"

#include <SDL3/SDL.h>  // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError, SDL_FRect

#include <algorithm>  // Para min
#include <cstddef>    // Para size_t
#include <fstream>    // Para basic_istream, basic_ifstream, istream, basic_ios, ifstream, istringstream, stringstream
#include <iostream>   // Para std::cout
#include <sstream>    // Para basic_istringstream, basic_stringstream
#include <stdexcept>  // Para runtime_error
#include <utility>    // Para move, pair

#include "core/rendering/texture.hpp"          // Para Texture
#include "core/resources/resource_helper.hpp"  // Para loadFile

// Carga las animaciones en un vector(Animations) desde un fichero
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer {
    // Intentar cargar desde ResourceHelper primero
    auto resource_data = ResourceHelper::loadFile(file_path);
    std::istringstream stream;
    bool using_resource_data = false;

    if (!resource_data.empty()) {
        std::string content(resource_data.begin(), resource_data.end());
        stream.str(content);
        using_resource_data = true;
    }

    // Fallback a archivo directo
    std::ifstream file;
    if (!using_resource_data) {
        file.open(file_path);
        if (!file) {
            std::cout << "Error: Fichero no encontrado " << file_path << '\n';
            throw std::runtime_error("Fichero no encontrado: " + file_path);
        }
    }

    std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);

    std::vector<std::string> buffer;
    std::string line;
    while (std::getline(input_stream, line)) {
        // Eliminar caracteres de retorno de carro (\r) al final de la línea
        if (!line.empty() && line.back() == '\r') {
            line.pop_back();
        }
        if (!line.empty()) {
            buffer.push_back(line);
        }
    }

    return buffer;
}

// Constructor
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const std::string& file_path)
    : MovingSprite(std::move(texture)) {
    // Carga las animaciones
    if (!file_path.empty()) {
        auto buffer = loadAnimationsFromFile(file_path);
        loadFromAnimationsFileBuffer(buffer);
    }
}

// Constructor
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer& animations)
    : MovingSprite(std::move(texture)) {
    if (!animations.empty()) {
        loadFromAnimationsFileBuffer(animations);
    }
}

// Obtiene el índice de la animación a partir del nombre
auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
    auto iterator = animation_indices_.find(name);
    if (iterator != animation_indices_.end()) {
        // Si se encuentra la animación en el mapa, devuelve su índice
        return iterator->second;
    }

    // Si no se encuentra, muestra una advertencia y devuelve -1
    std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
    return -1;
}

// Calcula el frame correspondiente a la animación (time-based)
void AnimatedSprite::animate(float delta_time) {
    if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
        return;
    }

    // Acumular tiempo transcurrido
    animations_[current_animation_].time_accumulator += delta_time;

    // Verificar si es momento de cambiar frame
    if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) {
        animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed;
        animations_[current_animation_].current_frame++;

        // Si alcanza el final de la animación
        if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
            if (animations_[current_animation_].loop == -1) {  // Si no hay loop, deja el último frame
                animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1;
                animations_[current_animation_].completed = true;
            } else {  // Si hay loop, vuelve al frame indicado
                animations_[current_animation_].time_accumulator = 0.0F;
                animations_[current_animation_].current_frame = animations_[current_animation_].loop;
            }
        }

        // Actualizar el sprite clip
        updateSpriteClip();
    }
}

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

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
    const auto NEW_ANIMATION = getAnimationIndex(name);
    if (current_animation_ != NEW_ANIMATION) {
        const auto OLD_ANIMATION = current_animation_;
        current_animation_ = NEW_ANIMATION;
        if (reset) {
            animations_[current_animation_].current_frame = 0;
            animations_[current_animation_].time_accumulator = 0.0F;
            animations_[current_animation_].completed = false;
        } else {
            animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
            animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
            animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
        }
        updateSpriteClip();
    }
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
    const auto NEW_ANIMATION = index;
    if (current_animation_ != NEW_ANIMATION) {
        const auto OLD_ANIMATION = current_animation_;
        current_animation_ = NEW_ANIMATION;
        if (reset) {
            animations_[current_animation_].current_frame = 0;
            animations_[current_animation_].time_accumulator = 0.0F;
            animations_[current_animation_].completed = false;
        } else {
            animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
            animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
            animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
        }
        updateSpriteClip();
    }
}

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

// Reinicia la animación
void AnimatedSprite::resetAnimation() {
    animations_[current_animation_].current_frame = 0;
    animations_[current_animation_].time_accumulator = 0.0F;
    animations_[current_animation_].completed = false;
    animations_[current_animation_].paused = false;
    updateSpriteClip();
}

// Carga la animación desde un vector de cadenas
void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source) {
    AnimationConfig config;

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

        if (line == "[animation]") {
            index = processAnimationBlock(source, index, config);
        } else {
            processConfigLine(line, config);
        }

        index++;
    }

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

    // Establece el primer frame inmediatamente si hay animaciones
    if (!animations_.empty()) {
        current_animation_ = 0;
        updateSpriteClip();
    }
}

// Procesa una línea de configuración
void AnimatedSprite::processConfigLine(const std::string& line, AnimationConfig& config) {
    size_t pos = line.find('=');
    if (pos == std::string::npos) {
        return;
    }

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

    if (key == "frame_width") {
        config.frame_width = value;
        updateFrameCalculations(config);
    } else if (key == "frame_height") {
        config.frame_height = value;
        updateFrameCalculations(config);
    } else {
        std::cout << "Warning: unknown parameter " << key << '\n';
    }
}

// Actualiza los cálculos basados en las dimensiones del frame
void AnimatedSprite::updateFrameCalculations(AnimationConfig& config) {
    config.frames_per_row = getTexture()->getWidth() / config.frame_width;
    const int WIDTH = getTexture()->getWidth() / config.frame_width;
    const int HEIGHT = getTexture()->getHeight() / config.frame_height;
    config.max_tiles = WIDTH * HEIGHT;
}

// Procesa un bloque completo de animación
auto AnimatedSprite::processAnimationBlock(const AnimationsFileBuffer& source, size_t start_index, const AnimationConfig& config) -> size_t {
    Animation animation;
    size_t index = start_index + 1;  // Salta la línea "[animation]"

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

        if (line == "[/animation]") {
            break;
        }

        processAnimationParameter(line, animation, config);
        index++;
    }

    // Añade la animación al vector de animaciones
    animations_.emplace_back(animation);

    // Rellena el mapa con el nombre y el nuevo índice
    animation_indices_[animation.name] = animations_.size() - 1;

    return index;
}

// Procesa un parámetro individual de animación
void AnimatedSprite::processAnimationParameter(const std::string& line, Animation& animation, const AnimationConfig& config) {
    size_t pos = line.find('=');
    if (pos == std::string::npos) {
        return;
    }

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

    if (key == "name") {
        animation.name = value;
    } else if (key == "speed") {
        animation.speed = std::stof(value);
    } else if (key == "loop") {
        animation.loop = std::stoi(value);
    } else if (key == "frames") {
        parseFramesParameter(value, animation, config);
    } else {
        std::cout << "Warning: unknown parameter " << key << '\n';
    }
}

// Parsea el parámetro de frames (lista separada por comas)
void AnimatedSprite::parseFramesParameter(const std::string& frames_str, Animation& animation, const AnimationConfig& config) {
    std::stringstream stream(frames_str);
    std::string tmp;
    SDL_FRect rect = {.x = 0, .y = 0, .w = config.frame_width, .h = config.frame_height};

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

// Establece la velocidad de la animación
void AnimatedSprite::setAnimationSpeed(float value) {
    animations_[current_animation_].speed = value;
}

// Actualiza el spriteClip con el frame correspondiente
void AnimatedSprite::updateSpriteClip() {
    setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}