#pragma once

#include <SDL2/SDL_render.h> // Para SDL_Renderer
#include <SDL2/SDL_stdinc.h> // Para Uint8, Uint32
#include <array>
#include <memory>
#include <string>

// Alias
using Palette = std::array<Uint32, 256>;

Palette loadPalette(const std::string &file_path);

struct SurfaceData
{
    Uint8 *data;   // Listado de indices de la paleta que conforman la imagen almacenada
    Uint16 width;  // Ancho de la imagen
    Uint16 height; // Alto de la imagen

    // Constructor por defecto
    SurfaceData() : data(nullptr), width(0), height(0) {}

    // Constructor que inicializa dimensiones y asigna memoria
    SurfaceData(Uint16 w, Uint16 h)
        : data(new Uint8[w * h]()), width(w), height(h) {}

    // Constructor para inicializar directamente con datos
    SurfaceData(Uint16 w, Uint16 h, Uint8 *pixels)
        : data(pixels), width(w), height(h) {}

    // Destructor para liberar memoria
    ~SurfaceData() { delete[] data; }

    // Evita copias accidentales (opcional para mayor seguridad)
    SurfaceData(const SurfaceData &) = delete;
    SurfaceData &operator=(const SurfaceData &) = delete;

    // Permite movimiento para evitar copias costosas
    SurfaceData(SurfaceData &&other) noexcept
        : data(other.data), width(other.width), height(other.height)
    {
        other.data = nullptr;
        other.width = 0;
        other.height = 0;
    }

    SurfaceData &operator=(SurfaceData &&other) noexcept
    {
        if (this != &other)
        {
            delete[] data;

            data = other.data;
            width = other.width;
            height = other.height;

            other.data = nullptr;
            other.width = 0;
            other.height = 0;
        }
        return *this;
    }
};

class Surface
{
private:
    std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
    Palette palette_;           // Paleta para volcar la SurfaceData a una Textura
    int transparent_color_;                     // Indice de la paleta que se omite en la copia de datos

public:
    // Constructor
    Surface(int w, int h);
    Surface(const std::string &file_path);

    // Destructor
    ~Surface() = default;

    // Carga una SurfaceData desde un archivo
    SurfaceData loadSurface(const std::string &file_path);

    // Carga una paleta desde un archivo
    void loadPalette(const std::string &file_path);
    void loadPalette(Palette palette);

    // Copia una región de la SurfaceData de origen a la SurfaceData de destino
    void render(int dx, int dy, int sx, int sy, int w, int h);
    void render(int x, int y, SDL_Rect *clip = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE);
    void render(SDL_Rect *srcRect = nullptr, SDL_Rect *dstRect = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE);

    // Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
    void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_Rect *srcRect = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE);

    // Establece un color en la paleta
    void setColor(int index, Uint32 color);

    // Rellena la SurfaceData con un color
    void clear(Uint8 color);

    // Vuelca la SurfaceData a una textura
    void copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture);

    // Realiza un efecto de fundido en la paleta
    bool fadePalette();

    // Pone un pixel en la SurfaceData
    void putPixel(int x, int y, Uint8 color);

    // Obtiene el color de un pixel de la surface_data
    Uint8 getPixel(int x, int y);

    // Dibuja un rectangulo
    void fillRect(SDL_Rect *rect, Uint8 color);

    // Dibuja una linea
    void drawLine(int x1, int y1, int x2, int y2, Uint8 color);

    // Metodos para gestionar surface_data_
    std::shared_ptr<SurfaceData> getSurfaceData() const { return surface_data_; }
    void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = new_data; }

    // Obtien ancho y alto
    int getWidth() const { return surface_data_->width; }
    int getHeight() const { return surface_data_->height; }
    
    // Color transparente
    int getTransparentColor() const { return transparent_color_; }
    void setTransparentColor(int color) { transparent_color_ = color; }
    
    // Paleta
    void setPalette(const std::array<Uint32, 256> &palette) { palette_ = palette; }
};
