#include "core/rendering/gif.hpp"

#include <cstring>    // Para memcpy, size_t
#include <iostream>   // Para std::cout
#include <stdexcept>  // Para runtime_error
#include <string>     // Para allocator, char_traits, operator==, basic_string

namespace GIF {

    // Función inline para reemplazar el macro READ.
    // Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
    inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) {
        std::memcpy(dst, buffer, size);
        buffer += size;
    }

    // Inicializa el diccionario LZW con los valores iniciales
    inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) {  // NOLINT(readability-identifier-naming)
        int size = 1 << code_length;
        dictionary.resize(1 << (code_length + 1));
        for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) {
            dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
            dictionary[dictionary_ind].prev = -1;
            dictionary[dictionary_ind].len = 1;
        }
        dictionary_ind += 2;  // Reservamos espacio para clear y stop codes
    }

    // Lee los próximos bits del stream de entrada para formar un código
    inline auto readNextCode(const uint8_t*& input, int& input_length, unsigned int& mask, int code_length) -> int {
        int code = 0;
        for (int i = 0; i < (code_length + 1); i++) {
            if (input_length <= 0) {
                throw std::runtime_error("Unexpected end of input in decompress");
            }
            int bit = ((*input & mask) != 0) ? 1 : 0;
            mask <<= 1;
            if (mask == 0x100) {
                mask = 0x01;
                input++;
                input_length--;
            }
            code |= (bit << i);
        }
        return code;
    }

    // Encuentra el primer byte de una cadena del diccionario
    inline auto findFirstByte(const std::vector<DictionaryEntry>& dictionary, int code) -> uint8_t {
        int ptr = code;
        while (dictionary[ptr].prev != -1) {
            ptr = dictionary[ptr].prev;
        }
        return dictionary[ptr].byte;
    }

    // Agrega una nueva entrada al diccionario
    inline void addDictionaryEntry(std::vector<DictionaryEntry>& dictionary, int& dictionary_ind, int& code_length, int prev, int code) {  // NOLINT(readability-identifier-naming)
        uint8_t first_byte;
        if (code == dictionary_ind) {
            first_byte = findFirstByte(dictionary, prev);
        } else {
            first_byte = findFirstByte(dictionary, code);
        }

        dictionary[dictionary_ind].byte = first_byte;
        dictionary[dictionary_ind].prev = prev;
        dictionary[dictionary_ind].len = dictionary[prev].len + 1;
        dictionary_ind++;

        if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
            code_length++;
            dictionary.resize(1 << (code_length + 1));
        }
    }

    // Escribe la cadena decodificada al buffer de salida
    inline auto writeDecodedString(const std::vector<DictionaryEntry>& dictionary, int code, uint8_t*& out) -> int {
        int cur_code = code;
        int match_len = dictionary[cur_code].len;
        while (cur_code != -1) {
            out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte;
            if (dictionary[cur_code].prev == cur_code) {
                std::cerr << "Internal error; self-reference detected." << '\n';
                throw std::runtime_error("Internal error in decompress: self-reference");
            }
            cur_code = dictionary[cur_code].prev;
        }
        out += match_len;
        return match_len;
    }

    void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) {
        // Verifica que el code_length tenga un rango razonable.
        if (code_length < 2 || code_length > 12) {
            throw std::runtime_error("Invalid LZW code length");
        }

        int prev = -1;
        std::vector<DictionaryEntry> dictionary;
        int dictionary_ind;
        unsigned int mask = 0x01;
        int reset_code_length = code_length;
        int clear_code = 1 << code_length;
        int stop_code = clear_code + 1;

        // Inicializamos el diccionario con el tamaño correspondiente.
        initializeDictionary(dictionary, code_length, dictionary_ind);

        // Bucle principal: procesar el stream comprimido.
        while (input_length > 0) {
            int code = readNextCode(input, input_length, mask, code_length);

            if (code == clear_code) {
                // Reinicia el diccionario.
                code_length = reset_code_length;
                initializeDictionary(dictionary, code_length, dictionary_ind);
                prev = -1;
                continue;
            }

            if (code == stop_code) {
                break;
            }

            if (prev > -1 && code_length < 12) {
                if (code > dictionary_ind) {
                    std::cerr << "code = " << std::hex << code
                              << ", but dictionary_ind = " << dictionary_ind << '\n';
                    throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
                }

                addDictionaryEntry(dictionary, dictionary_ind, code_length, prev, code);
            }

            prev = code;

            // Verifica que 'code' sea un índice válido antes de usarlo.
            if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
                std::cerr << "Invalid LZW code " << code
                          << ", dictionary size " << dictionary.size() << '\n';
                throw std::runtime_error("LZW error: invalid code encountered");
            }

            writeDecodedString(dictionary, code, out);
        }
    }

    auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t> {
        std::vector<uint8_t> data;
        uint8_t block_size = *buffer;
        buffer++;
        while (block_size != 0) {
            data.insert(data.end(), buffer, buffer + block_size);
            buffer += block_size;
            block_size = *buffer;
            buffer++;
        }
        return data;
    }

    auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t> {
        ImageDescriptor image_descriptor;
        // Lee 9 bytes para el image descriptor.
        readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));

        uint8_t lzw_code_size;
        readBytes(buffer, &lzw_code_size, sizeof(uint8_t));

        std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
        int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
        std::vector<uint8_t> uncompressed_data(uncompressed_data_length);

        decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
        return uncompressed_data;
    }

    auto Gif::loadPalette(const uint8_t* buffer) -> std::vector<uint32_t> {
        uint8_t header[6];
        std::memcpy(header, buffer, 6);
        buffer += 6;

        ScreenDescriptor screen_descriptor;
        std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
        buffer += sizeof(ScreenDescriptor);

        std::vector<uint32_t> global_color_table;
        if ((screen_descriptor.fields & 0x80) != 0) {
            int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
            global_color_table.resize(global_color_table_size);
            for (int i = 0; i < global_color_table_size; ++i) {
                uint8_t r = buffer[0];
                uint8_t g = buffer[1];
                uint8_t b = buffer[2];
                global_color_table[i] = (r << 16) | (g << 8) | b;
                buffer += 3;
            }
        }
        return global_color_table;
    }

    auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> {
        // Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
        uint8_t header[6];
        std::memcpy(header, buffer, 6);
        buffer += 6;

        // Opcional: Validar header
        std::string header_str(reinterpret_cast<char*>(header), 6);
        if (header_str != "GIF87a" && header_str != "GIF89a") {
            throw std::runtime_error("Formato de archivo GIF inválido.");
        }

        // Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
        ScreenDescriptor screen_descriptor;
        readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));

        // Asigna ancho y alto
        w = screen_descriptor.width;
        h = screen_descriptor.height;

        int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
        std::vector<RGB> global_color_table;
        if ((screen_descriptor.fields & 0x80) != 0) {
            int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
            global_color_table.resize(global_color_table_size);
            std::memcpy(global_color_table.data(), buffer, static_cast<size_t>(3) * static_cast<size_t>(global_color_table_size));
            buffer += static_cast<ptrdiff_t>(3) * global_color_table_size;
        }

        // Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
        uint8_t block_type = *buffer++;
        while (block_type != TRAILER) {
            if (block_type == EXTENSION_INTRODUCER)  // 0x21
            {
                // Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
                uint8_t extension_label = *buffer++;
                switch (extension_label) {
                    case GRAPHIC_CONTROL:  // 0xF9
                    {
                        // Procesar Graphic Control Extension:
                        uint8_t block_size = *buffer++;  // Normalmente, blockSize == 4
                        buffer += block_size;            // Saltamos los 4 bytes del bloque fijo
                        // Saltar los sub-bloques
                        uint8_t sub_block_size = *buffer++;
                        while (sub_block_size != 0) {
                            buffer += sub_block_size;
                            sub_block_size = *buffer++;
                        }
                        break;
                    }
                    case APPLICATION_EXTENSION:  // 0xFF
                    case COMMENT_EXTENSION:      // 0xFE
                    case PLAINTEXT_EXTENSION:    // 0x01
                    {
                        // Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
                        uint8_t block_size = *buffer++;
                        buffer += block_size;
                        uint8_t sub_block_size = *buffer++;
                        while (sub_block_size != 0) {
                            buffer += sub_block_size;
                            sub_block_size = *buffer++;
                        }
                        break;
                    }
                    default: {
                        // Si la etiqueta de extensión es desconocida, saltarla también:
                        uint8_t block_size = *buffer++;
                        buffer += block_size;
                        uint8_t sub_block_size = *buffer++;
                        while (sub_block_size != 0) {
                            buffer += sub_block_size;
                            sub_block_size = *buffer++;
                        }
                        break;
                    }
                }
            } else if (block_type == IMAGE_DESCRIPTOR) {
                // Procesar el Image Descriptor y retornar los datos de imagen
                return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
            } else {
                std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << '\n';
                return std::vector<uint8_t>{};
            }
            block_type = *buffer++;
        }

        return std::vector<uint8_t>{};
    }

    auto Gif::loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> {
        return processGifStream(buffer, w, h);
    }

}  // namespace GIF
