#include "core/jail/jdraw8.hpp"

#include <fstream>
#include <string>

#include "core/resources/resource_cache.hpp"
#include "core/resources/resource_helper.hpp"
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#endif
// NOLINTBEGIN(clang-analyzer-unix.Malloc): codi extern de tercers, no l'auditem.
#include "external/gif.h"
// NOLINTEND(clang-analyzer-unix.Malloc)
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

Jd8::Surface screen = nullptr;
Jd8::Palette main_palette = nullptr;
Uint32* pixel_data = nullptr;

void Jd8::init() {
    screen = new Uint8[64000]{};
    main_palette = new Color[256]{};
    pixel_data = new Uint32[std::size_t{320} * 200]{};
}

void Jd8::quit() {
    delete[] screen;
    delete[] main_palette;
    delete[] pixel_data;
    screen = nullptr;
    main_palette = nullptr;
    pixel_data = nullptr;
}

void Jd8::clearScreen(Uint8 color) {
    memset(screen, color, 64000);
}

auto Jd8::newSurface() -> Jd8::Surface {
    return new Uint8[64000]{};
}

// Helper intern: deriva el basename d'una ruta per a buscar al Cache.
static auto pathBasename(const char* file) -> std::string {
    std::string s = file;
    auto pos = s.find_last_of("/\\");
    return pos == std::string::npos ? s : s.substr(pos + 1);
}

auto Jd8::loadSurface(const char* file) -> Jd8::Surface {
    // Prova primer el Resource::Cache. Si l'asset és precarregat, copiem
    // els 64KB des del cache (microsegons) i ens estalviem la decodificació
    // GIF. Mantenim el contracte de la funció: el caller rep un buffer
    // fresc que ha d'alliberar amb Jd8::freeSurface.
    if (Resource::Cache::get() != nullptr) {
        try {
            const auto& cached = Resource::Cache::get()->getSurfacePixels(pathBasename(file));
            Jd8::Surface image = Jd8::newSurface();
            memcpy(image, cached.data(), 64000);
            return image;
        } catch (const std::exception&) {
            // @INTENTIONAL: no està al cache (asset no llistat al manifest), fallback al loader.
        }
    }

    auto buffer = ResourceHelper::loadFile(file);
    unsigned short w;
    unsigned short h;
    Uint8* pixels = LoadGif(buffer.data(), &w, &h);
    if (pixels == nullptr) {
        printf("Unable to load bitmap: %s\n", SDL_GetError());
        exit(1);
    }
    Jd8::Surface image = Jd8::newSurface();
    memcpy(image, pixels, 64000);
    free(pixels);
    return image;
}

auto Jd8::loadPalette(const char* file) -> Jd8::Palette {
    // Sempre retorna un buffer de 256 colors reservat amb `new Color[256]`
    // — el caller és responsable d'alliberar-lo amb `delete[]` (o lliurar-ne
    // l'ownership a `Jd8::setScreenPalette`).
    auto* palette = new Color[256];

    if (Resource::Cache::get() != nullptr) {
        try {
            const auto& cached = Resource::Cache::get()->getPaletteBytes(pathBasename(file));
            memcpy(palette, cached.data(), 768);
            return palette;
        } catch (const std::exception&) {
            // @INTENTIONAL: no està al cache, fallback a lectura + LoadPalette.
        }
    }

    auto buffer = ResourceHelper::loadFile(file);
    Uint8* raw = LoadPalette(buffer.data());  // external malloc
    memcpy(palette, raw, 768);
    free(raw);
    return palette;
}

void Jd8::setScreenPalette(Jd8::Palette palette) {
    if (main_palette == palette) {
        return;
    }
    delete[] main_palette;
    main_palette = palette;
}

void Jd8::fillSquare(int ini, int height, Uint8 color) {
    const int OFFSET = ini * 320;
    const int SIZE = height * 320;
    memset(&screen[OFFSET], color, SIZE);
}

void Jd8::fillRect(int x, int y, int w, int h, Uint8 color) {
    if (x < 0) {
        w += x;
        x = 0;
    }
    if (y < 0) {
        h += y;
        y = 0;
    }
    if (x + w > 320) {
        w = 320 - x;
    }
    if (y + h > 200) {
        h = 200 - y;
    }
    if (w <= 0 || h <= 0) {
        return;
    }
    for (int row = y; row < y + h; ++row) {
        memset(&screen[x + (row * 320)], color, w);
    }
}

void Jd8::blit(const Uint8* surface) {
    memcpy(screen, surface, 64000);
}

void Jd8::blit(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh) {
    int src_pointer = sx + (sy * 320);
    int dst_pointer = x + (y * 320);
    for (int i = 0; i < sh; i++) {
        memcpy(&screen[dst_pointer], &surface[src_pointer], sw);
        src_pointer += 320;
        dst_pointer += 320;
    }
}

void Jd8::blitToSurface(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Jd8::Surface dest) {
    int src_pointer = sx + (sy * 320);
    int dst_pointer = x + (y * 320);
    for (int i = 0; i < sh; i++) {
        memcpy(&dest[dst_pointer], &surface[src_pointer], sw);
        src_pointer += 320;
        dst_pointer += 320;
    }
}

void Jd8::blitCK(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
    int src_pointer = sx + (sy * 320);
    int dst_pointer = x + (y * 320);
    for (int j = 0; j < sh; j++) {
        for (int i = 0; i < sw; i++) {
            if (surface[src_pointer + i] != colorkey) {
                screen[dst_pointer + i] = surface[src_pointer + i];
            }
        }
        src_pointer += 320;
        dst_pointer += 320;
    }
}

void Jd8::blitCKCut(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
    int src_pointer = sx + (sy * 320);
    int dst_pointer = x + (y * 320);
    for (int j = 0; j < sh; j++) {
        for (int i = 0; i < sw; i++) {
            if (surface[src_pointer + i] != colorkey && (x + i >= 0) && (y + j >= 0) && (x + i < 320) && (y + j < 200)) {
                screen[dst_pointer + i] = surface[src_pointer + i];
            }
        }
        src_pointer += 320;
        dst_pointer += 320;
    }
}

void Jd8::blitCKScroll(int y, const Uint8* surface, int sx, int sy, int sh, Uint8 colorkey) {
    int dst_pointer = y * 320;
    for (int j = sy; j < sy + sh; j++) {
        for (int i = 0; i < 320; i++) {
            int x = (i + sx) % 320;
            if (surface[x + (j * 320)] != colorkey) {
                screen[dst_pointer] = surface[x + (j * 320)];
            }
            dst_pointer++;
        }
    }
}

void Jd8::blitCKToSurface(int x, int y, const Uint8* surface, int sx, int sy, int sw, int sh, Jd8::Surface dest, Uint8 colorkey) {
    int src_pointer = sx + (sy * 320);
    int dst_pointer = x + (y * 320);
    for (int j = 0; j < sh; j++) {
        for (int i = 0; i < sw; i++) {
            if (surface[src_pointer + i] != colorkey) {
                dest[dst_pointer + i] = surface[src_pointer + i];
            }
        }
        src_pointer += 320;
        dst_pointer += 320;
    }
}

void Jd8::flip() {
    // Converteix el framebuffer indexat (paletted) a ARGB (pixel_data).
    // El Director crida aquesta funció després del tick de cada escena
    // per preparar el frame abans de presentar-lo. Ja no fa yield —
    // tot corre en un sol thread sense fibers des de Phase B.2.
    for (int x = 0; x < 320; x++) {
        for (int y = 0; y < 200; y++) {
            Uint32 color = 0xFF000000 + main_palette[screen[x + (y * 320)]].r + (main_palette[screen[x + (y * 320)]].g << 8) + (main_palette[screen[x + (y * 320)]].b << 16);
            pixel_data[x + (y * 320)] = color;
        }
    }
}

auto Jd8::getFramebuffer() -> Uint32* {
    return pixel_data;
}

void Jd8::freeSurface(Jd8::Surface surface) {  // NOLINT(readability-non-const-parameter): allibera memòria, no pot ser const
    delete[] surface;
}

auto Jd8::getPixel(const Uint8* surface, int x, int y) -> Uint8 {
    return surface[x + (y * 320)];
}

void Jd8::putPixel(Jd8::Surface surface, int x, int y, Uint8 pixel) {
    surface[x + (y * 320)] = pixel;
}

void Jd8::setPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b) {
    main_palette[index].r = r << 2;
    main_palette[index].g = g << 2;
    main_palette[index].b = b << 2;
}

// Màquina d'estats del fade. Evita que JD8_FadeOut/JD8_FadeToPal hagen de
// mantindre whiles interns. Cada pas aplica un delta a la paleta activa i
// el caller decideix quan fer Flip.
namespace {

    enum class FadeType : std::uint8_t {
        NONE = 0,
        OUT,
        TO_PAL,
    };

    constexpr int FADE_STEPS = 32;

    FadeType fade_type = FadeType::NONE;
    Color fade_target[256];
    int fade_step = 0;

    void applyFadeStep() {
        if (fade_type == FadeType::OUT) {
            for (int i = 0; i < 256; i++) {
                main_palette[i].r = main_palette[i].r >= 8 ? main_palette[i].r - 8 : 0;
                main_palette[i].g = main_palette[i].g >= 8 ? main_palette[i].g - 8 : 0;
                main_palette[i].b = main_palette[i].b >= 8 ? main_palette[i].b - 8 : 0;
            }
        } else if (fade_type == FadeType::TO_PAL) {
            for (int i = 0; i < 256; i++) {
                main_palette[i].r = main_palette[i].r <= int(fade_target[i].r) - 8
                    ? main_palette[i].r + 8
                    : fade_target[i].r;
                main_palette[i].g = main_palette[i].g <= int(fade_target[i].g) - 8
                    ? main_palette[i].g + 8
                    : fade_target[i].g;
                main_palette[i].b = main_palette[i].b <= int(fade_target[i].b) - 8
                    ? main_palette[i].b + 8
                    : fade_target[i].b;
            }
        }
    }

}  // namespace

void Jd8::fadeStartOut() {
    fade_type = FadeType::OUT;
    fade_step = 0;
}

void Jd8::fadeStartToPal(const Color* pal) {
    fade_type = FadeType::TO_PAL;
    memcpy(fade_target, pal, sizeof(Color) * 256);
    fade_step = 0;
}

auto Jd8::fadeIsActive() -> bool {
    return fade_type != FadeType::NONE;
}

auto Jd8::fadeTickStep() -> bool {
    if (fade_type == FadeType::NONE) {
        return true;
    }

    applyFadeStep();
    fade_step++;

    if (fade_step >= FADE_STEPS) {
        fade_type = FadeType::NONE;
        return true;
    }
    return false;
}

// Els shims bloquejants `JD8_FadeOut` i `JD8_FadeToPal` han estat
// eliminats a Phase B.2: feien un bucle de 32 iteracions amb `Jd8::flip`
// entre cada una que només funcionava mentre l'entorn tenia fibers i
// `Jd8::flip` cedia el control al Director. Ara tot fade es fa tick a
// tick via `Scenes::PaletteFade` (que encapsula `Jd8::fadeStartOut` /
// `Jd8::fadeStartToPal` + `Jd8::fadeTickStep`).
