#include "surface.h"
#include <SDL2/SDL_pixels.h> // Para SDL_PIXELFORMAT_ARGB8888
#include <SDL2/SDL_rect.h>   // Para SDL_Rect
#include <fstream>           // Para std::ifstream
#include <iostream>          // Para std::cerr
#include <vector>            // Para std::vector
#include <stdexcept>         // For exceptions
#include "gif.h"             // Para LoadGif, LoadPalette
#include <memory>            // Para std::shared_ptr
#include <algorithm>         // Para std::copy
#include "asset.h"

Surface::Surface(std::shared_ptr<SurfaceData> surface_dest, int w, int h)
    : surface_dest_(surface_dest),
      surface_(std::make_shared<SurfaceData>(w, h)),
      transparent_color_(0) {}

Surface::Surface(std::shared_ptr<SurfaceData> surface_dest, const std::string &file_path)
    : surface_dest_(surface_dest),
      surface_(std::make_shared<SurfaceData>(loadSurface(Asset::get()->get(file_path)))),
      transparent_color_(0) {}

Surface::~Surface() {}

// Carga una superficie desde un archivo
SurfaceData Surface::loadSurface(const std::string &file_path)
{
    std::ifstream file(file_path, std::ios::binary | std::ios::ate);

    if (!file.is_open())
    {
        std::cerr << "Error opening file: " << file_path << std::endl;
        throw std::runtime_error("Error opening file");
    }

    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<Uint8> buffer(size);
    if (!file.read((char *)buffer.data(), size))
    {
        std::cerr << "Error reading file: " << file_path << std::endl;
        throw std::runtime_error("Error reading file");
    }

    Uint16 w, h;
    Uint8 *pixels = LoadGif(buffer.data(), &w, &h);
    if (pixels == nullptr)
    {
        std::cerr << "Error loading GIF from file: " << file_path << std::endl;
        throw std::runtime_error("Error loading GIF");
    }

    // Crear y devolver directamente el objeto SurfaceData
    return SurfaceData(w, h, pixels);
}

// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string &file_path)
{
    // Abrir el archivo en modo binario
    std::ifstream file(file_path, std::ios::binary | std::ios::ate);
    if (!file.is_open())
    {
        throw std::runtime_error("Error opening file: " + file_path);
    }

    // Leer el contenido del archivo en un buffer
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<Uint8> buffer(size);
    if (!file.read(reinterpret_cast<char *>(buffer.data()), size))
    {
        throw std::runtime_error("Error reading file: " + file_path);
    }

    // Cargar la paleta usando los datos del buffer
    std::unique_ptr<Uint32[]> pal(LoadPalette(buffer.data()));
    if (pal == nullptr)
    {
        throw std::runtime_error("Error loading palette from file: " + file_path);
    }

    // Copiar los datos de la paleta al std::array
    std::copy(pal.get(), pal.get() + palette_.size(), palette_.begin());

    for (auto p : palette_)
    {
        std::cout << std::hex << p << " ";
    }
    std::cout << std::endl;
}

// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color)
{
    palette_.at(index) = color;
}

// Limpia la superficie de destino con un color
void Surface::clear(std::shared_ptr<SurfaceData> surface, Uint8 color)
{
    const size_t total_pixels = surface->width * surface->height;
    std::fill(surface->data, surface->data + total_pixels, color);
}

// Pone un pixel en la superficie de destino
void Surface::putPixel(int x, int y, Uint8 color)
{
    if (color == transparent_color_)
    {
        return; // Color transparente, no dibujar
    }

    if (x < 0 || y < 0 || x >= surface_dest_->width || y >= surface_dest_->height)
    {
        return; // Coordenadas fuera de rango
    }

    const int index = x + y * surface_dest_->width;
    surface_dest_->data[index] = color;
}

// Obtiene el color de un pixel de la superficie de origen
Uint8 Surface::getPixel(int x, int y)
{
    return surface_->data[x + y * surface_->width];
}

// Copia una región de la superficie de origen a la de destino
void Surface::render(int dx, int dy, int sx, int sy, int w, int h)
{
    if (!surface_ || !surface_dest_)
    {
        throw std::runtime_error("Surface source or destination is null.");
    }

    // Limitar la región para evitar accesos fuera de rango
    w = std::min(w, surface_->width - sx);
    h = std::min(h, surface_->height - sy);
    w = std::min(w, surface_dest_->width - dx);
    h = std::min(h, surface_dest_->height - dy);

    for (int iy = 0; iy < h; ++iy)
    {
        for (int ix = 0; ix < w; ++ix)
        {
            Uint8 color = surface_->data[(sx + ix) + (sy + iy) * surface_->width];
            if (color != transparent_color_) // Opcional: Ignorar píxeles transparentes
            {
                surface_dest_->data[(dx + ix) + (dy + iy) * surface_dest_->width] = color;
            }
        }
    }
}

/*
// Vuelca la superficie a una textura
void Surface::flip(SDL_Renderer *renderer, SDL_Texture *texture)
{
    Uint32 *pixels;
    int pitch;
    SDL_LockTexture(texture, nullptr, (void **)&pixels, &pitch);
    for (int i = 0; i < surface_.width * surface_.height; ++i)
    {
        pixels[i] = palette_[surface_.data[i]];
        }
        SDL_UnlockTexture(texture);
        SDL_RenderCopy(renderer, texture, nullptr, nullptr);
        }
        */

// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture)
{
    if (!renderer || !texture)
    {
        throw std::runtime_error("Renderer or texture is null.");
    }

    if (surface_->width <= 0 || surface_->height <= 0 || !surface_->data)
    {
        throw std::runtime_error("Invalid surface dimensions or data.");
    }

    Uint32 *pixels = nullptr;
    int pitch = 0;

    // Bloquea la textura para modificar los píxeles directamente
    if (SDL_LockTexture(texture, nullptr, (void **)&pixels, &pitch) != 0)
    {
        throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
    }

    // Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
    int row_stride = pitch / sizeof(Uint32);

    for (int y = 0; y < surface_->height; ++y)
    {
        for (int x = 0; x < surface_->width; ++x)
        {
            // Calcular la posición correcta en la textura teniendo en cuenta el stride
            int texture_index = y * row_stride + x;
            int surface_index = y * surface_->width + x;

            pixels[texture_index] = palette_[surface_->data[surface_index]];
        }
    }

    SDL_UnlockTexture(texture); // Desbloquea la textura

    // Renderiza la textura en la pantalla completa
    if (SDL_RenderCopy(renderer, texture, nullptr, nullptr) != 0)
    {
        throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
    }
}

// Realiza un efecto de fundido en la paleta
bool Surface::fadePalette()
{
    // Verificar que el tamaño mínimo de palette_ sea adecuado
    static constexpr int palette_size = 19;
    if (sizeof(palette_) / sizeof(palette_[0]) < palette_size)
    {
        throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
    }

    // Desplazar colores (pares e impares)
    for (int i = 18; i > 1; --i)
    {
        palette_[i] = palette_[i - 2];
    }

    // Ajustar el primer color
    palette_[1] = palette_[0];

    // Devolver si el índice 15 coincide con el índice 0
    return palette_[15] == palette_[0];
}
