#include "gif.h"
#include <SDL3/SDL_log.h> // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo
#include <cstring>        // Para memcpy, size_t
#include <stdexcept>      // Para runtime_error
#include <string>         // Para char_traits, operator==, basic_string, string

namespace GIF
{
    inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
    {
        std::memcpy(dst, buffer, size);
        buffer += size;
    }

    void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
    {
        if (code_length < 2 || code_length > 12)
        {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid LZW code length: %d", code_length);
            throw std::runtime_error("Invalid LZW code length");
        }

        int i, bit;
        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;
        int match_len = 0;

        dictionary.resize(1 << (code_length + 1));
        for (dictionary_ind = 0; dictionary_ind < (1 << code_length); 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;

        while (input_length > 0)
        {
            int code = 0;
            for (i = 0; i < (code_length + 1); i++)
            {
                if (input_length <= 0)
                {
                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unexpected end of input in decompress");
                    throw std::runtime_error("Unexpected end of input in decompress");
                }
                bit = ((*input & mask) != 0) ? 1 : 0;
                mask <<= 1;
                if (mask == 0x100)
                {
                    mask = 0x01;
                    input++;
                    input_length--;
                }
                code |= (bit << i);
            }

            if (code == clear_code)
            {
                code_length = reset_code_length;
                dictionary.resize(1 << (code_length + 1));
                for (dictionary_ind = 0; dictionary_ind < (1 << code_length); 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;
                prev = -1;
                continue;
            }
            else if (code == stop_code)
            {
                break;
            }

            if (prev > -1 && code_length < 12)
            {
                if (code > dictionary_ind)
                {
                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "LZW error: code (%d) exceeds dictionary_ind (%d)", code, dictionary_ind);
                    throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
                }

                int ptr;
                if (code == dictionary_ind)
                {
                    ptr = prev;
                    while (dictionary[ptr].prev != -1)
                        ptr = dictionary[ptr].prev;
                    dictionary[dictionary_ind].byte = dictionary[ptr].byte;
                }
                else
                {
                    ptr = code;
                    while (dictionary[ptr].prev != -1)
                        ptr = dictionary[ptr].prev;
                    dictionary[dictionary_ind].byte = dictionary[ptr].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));
                }
            }

            prev = code;

            if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
            {
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid LZW code %d, dictionary size %lu", code, static_cast<unsigned long>(dictionary.size()));
                throw std::runtime_error("LZW error: invalid code encountered");
            }

            int curCode = code;
            match_len = dictionary[curCode].len;
            while (curCode != -1)
            {
                out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
                if (dictionary[curCode].prev == curCode)
                {
                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Internal error; self-reference detected.");
                    throw std::runtime_error("Internal error in decompress: self-reference");
                }
                curCode = dictionary[curCode].prev;
            }
            out += match_len;
        }
    }

    std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
    {
        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;
    }

    std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
    {
        ImageDescriptor 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;
    }

    std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
    {
        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)
        {
            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;
    }

    std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
    {
        uint8_t header[6];
        std::memcpy(header, buffer, 6);
        buffer += 6;

        std::string headerStr(reinterpret_cast<char *>(header), 6);
        if (headerStr != "GIF87a" && headerStr != "GIF89a")
        {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Formato de archivo GIF inválido: %s", headerStr.c_str());
            throw std::runtime_error("Formato de archivo GIF inválido.");
        }

        SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Procesando GIF con cabecera: %s", headerStr.c_str());

        ScreenDescriptor screen_descriptor;
        readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));

        w = screen_descriptor.width;
        h = screen_descriptor.height;

        SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Resolución del GIF: %dx%d", w, h);

        int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
        std::vector<RGB> global_color_table;
        if (screen_descriptor.fields & 0x80)
        {
            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, 3 * global_color_table_size);
            buffer += 3 * global_color_table_size;
        }

        uint8_t block_type = *buffer++;
        while (block_type != TRAILER)
        {
            if (block_type == EXTENSION_INTRODUCER)
            {
                uint8_t extension_label = *buffer++;
                switch (extension_label)
                {
                case GRAPHIC_CONTROL:
                {
                    uint8_t blockSize = *buffer++;
                    buffer += blockSize;
                    uint8_t subBlockSize = *buffer++;
                    while (subBlockSize != 0)
                    {
                        buffer += subBlockSize;
                        subBlockSize = *buffer++;
                    }
                    break;
                }
                case APPLICATION_EXTENSION:
                case COMMENT_EXTENSION:
                case PLAINTEXT_EXTENSION:
                {
                    uint8_t blockSize = *buffer++;
                    buffer += blockSize;
                    uint8_t subBlockSize = *buffer++;
                    while (subBlockSize != 0)
                    {
                        buffer += subBlockSize;
                        subBlockSize = *buffer++;
                    }
                    break;
                }
                default:
                {
                    uint8_t blockSize = *buffer++;
                    buffer += blockSize;
                    uint8_t subBlockSize = *buffer++;
                    while (subBlockSize != 0)
                    {
                        buffer += subBlockSize;
                        subBlockSize = *buffer++;
                    }
                    break;
                }
                }
            }
            else if (block_type == IMAGE_DESCRIPTOR)
            {
                return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
            }
            else
            {
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unrecognized block type: 0x%X", block_type);
                return std::vector<uint8_t>{};
            }
            block_type = *buffer++;
        }

        SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "GIF procesado correctamente.");
        return std::vector<uint8_t>{};
    }

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

} // namespace GIF
