
#include "core/rendering/texture.h"

#include <SDL3/SDL.h>
#include <stdlib.h>  // for exit

#include <iostream>  // for basic_ostream, operator<<, cout, endl
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb_image.h"  // for stbi_failure_reason, stbi_image_free

SDL_ScaleMode Texture::currentScaleMode = SDL_SCALEMODE_NEAREST;

void Texture::setGlobalScaleMode(SDL_ScaleMode mode) {
    currentScaleMode = mode;
}

// Constructor
Texture::Texture(SDL_Renderer *renderer, const std::string &path, bool verbose)
    : texture(nullptr),
      renderer(renderer),
      width(0),
      height(0),
      path(path) {
    // Carga el fichero en la textura
    if (!path.empty()) {
        loadFromFile(path, renderer, verbose);
    }
}

// Constructor desde bytes
Texture::Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose)
    : texture(nullptr),
      renderer(renderer),
      width(0),
      height(0),
      path("") {
    if (!bytes.empty()) {
        loadFromMemory(bytes.data(), bytes.size(), renderer, verbose);
    }
}

// Destructor
Texture::~Texture() {
    // Libera memoria
    unload();
}

// Helper: convierte píxeles RGBA decodificados por stbi en SDL_Texture
static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned char *data, int w, int h, int *out_w, int *out_h) {
    const int pitch = 4 * w;
    SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, static_cast<void *>(data), pitch);
    if (loadedSurface == nullptr) {
        return nullptr;
    }
    SDL_Texture *newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
    if (newTexture != nullptr) {
        *out_w = loadedSurface->w;
        *out_h = loadedSurface->h;
        SDL_SetTextureScaleMode(newTexture, Texture::currentScaleMode);
    }
    SDL_DestroySurface(loadedSurface);
    return newTexture;
}

// Carga una imagen desde un fichero
bool Texture::loadFromFile(const std::string &path, SDL_Renderer *renderer, bool verbose) {
    const std::string filename = path.substr(path.find_last_of("\\/") + 1);
    int req_format = STBI_rgb_alpha;
    int w, h, orig_format;
    unsigned char *data = stbi_load(path.c_str(), &w, &h, &orig_format, req_format);
    if (data == nullptr) {
        SDL_Log("Loading image failed: %s", stbi_failure_reason());
        exit(1);
    } else if (verbose) {
        std::cout << "Image loaded: " << filename.c_str() << std::endl;
    }

    unload();
    SDL_Texture *newTexture = createTextureFromPixels(renderer, data, w, h, &this->width, &this->height);
    if (newTexture == nullptr && verbose) {
        std::cout << "Unable to load image " << path.c_str() << std::endl;
    }

    stbi_image_free(data);
    texture = newTexture;
    return texture != nullptr;
}

// Carga una imagen desde bytes en memoria
bool Texture::loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose) {
    int w, h, orig_format;
    unsigned char *pixels = stbi_load_from_memory(data, (int)size, &w, &h, &orig_format, STBI_rgb_alpha);
    if (pixels == nullptr) {
        SDL_Log("Loading image from memory failed: %s", stbi_failure_reason());
        return false;
    }

    unload();
    SDL_Texture *newTexture = createTextureFromPixels(renderer, pixels, w, h, &this->width, &this->height);
    if (newTexture == nullptr && verbose) {
        std::cout << "Unable to create texture from memory" << std::endl;
    }

    stbi_image_free(pixels);
    texture = newTexture;
    return texture != nullptr;
}

// Crea una textura en blanco
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
    // Crea una textura sin inicializar
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, access, width, height);
    if (texture == nullptr) {
        std::cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << std::endl;
    } else {
        this->width = width;
        this->height = height;
        SDL_SetTextureScaleMode(texture, currentScaleMode);
    }

    return texture != nullptr;
}

// Libera la memoria de la textura
void Texture::unload() {
    // Libera la textura si existe
    if (texture != nullptr) {
        SDL_DestroyTexture(texture);
        texture = nullptr;
        width = 0;
        height = 0;
    }
}

// Establece el color para la modulacion
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) {
    SDL_SetTextureColorMod(texture, red, green, blue);
}

// Establece el blending
void Texture::setBlendMode(SDL_BlendMode blending) {
    SDL_SetTextureBlendMode(texture, blending);
}

// Establece el alpha para la modulación
void Texture::setAlpha(Uint8 alpha) {
    SDL_SetTextureAlphaMod(texture, alpha);
}

// Renderiza la textura en un punto específico
void Texture::render(SDL_Renderer *renderer, int x, int y, SDL_Rect *clip, float zoomW, float zoomH, double angle, SDL_Point *center, SDL_FlipMode flip) {
    // Establece el destino de renderizado en la pantalla
    SDL_FRect renderQuad = {(float)x, (float)y, (float)width, (float)height};

    // Obtiene las dimesiones del clip de renderizado
    if (clip != nullptr) {
        renderQuad.w = (float)clip->w;
        renderQuad.h = (float)clip->h;
    }

    renderQuad.w = renderQuad.w * zoomW;
    renderQuad.h = renderQuad.h * zoomH;

    // Convierte el clip a SDL_FRect
    SDL_FRect srcRect;
    SDL_FRect *srcRectPtr = nullptr;
    if (clip != nullptr) {
        srcRect = {(float)clip->x, (float)clip->y, (float)clip->w, (float)clip->h};
        srcRectPtr = &srcRect;
    }

    // Convierte el centro a SDL_FPoint
    SDL_FPoint fCenter;
    SDL_FPoint *fCenterPtr = nullptr;
    if (center != nullptr) {
        fCenter = {(float)center->x, (float)center->y};
        fCenterPtr = &fCenter;
    }

    // Renderiza a pantalla
    SDL_RenderTextureRotated(renderer, texture, srcRectPtr, &renderQuad, angle, fCenterPtr, flip);
}

// Establece la textura como objetivo de renderizado
void Texture::setAsRenderTarget(SDL_Renderer *renderer) {
    SDL_SetRenderTarget(renderer, texture);
}

// Obtiene el ancho de la imagen
int Texture::getWidth() {
    return width;
}

// Obtiene el alto de la imagen
int Texture::getHeight() {
    return height;
}

// Recarga la textura
bool Texture::reLoad() {
    return loadFromFile(path, renderer);
}

// Obtiene la textura
SDL_Texture *Texture::getSDLTexture() {
    return texture;
}