/**
 * @file bmp_decoder.hpp
 * @brief Native C++ BMP decoder (no external dependencies)
 *
 * Features:
 * - Pure C++ implementation (no external library required)
 * - Supports uncompressed and RLE-compressed RGB/RGBA formats
 * - Bottom-up and top-down bitmap orientations
 * - 8-bit, 24-bit and 32-bit color depths
 * - RLE4 and RLE8 compression support
 * - Direct RGB output
 * - Zero-copy where possible
 * - Thread-safe (one instance per thread)
 *
 * Compression support:
 * - BI_RGB (uncompressed)
 * - BI_RLE8 (8-bit run-length encoding)
 * - BI_RLE4 (4-bit run-length encoding)
 */

#pragma once

#include "../core/sample.hpp"
#include "../core/buffer_pool.hpp"
#include "../core/compat.hpp"  // span polyfill for C++17
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

namespace turboloader {

/**
 * @brief BMP file header (14 bytes)
 */
#pragma pack(push, 1)
struct BMPFileHeader {
    uint16_t type;         // Magic: 'BM' (0x4D42)
    uint32_t size;         // File size in bytes
    uint16_t reserved1;    // Reserved (0)
    uint16_t reserved2;    // Reserved (0)
    uint32_t off_bits;     // Offset to pixel data
};
#pragma pack(pop)

/**
 * @brief BMP info header (40 bytes - BITMAPINFOHEADER)
 */
#pragma pack(push, 1)
struct BMPInfoHeader {
    uint32_t size;             // Header size (40)
    int32_t  width;            // Image width
    int32_t  height;           // Image height (negative = top-down)
    uint16_t planes;           // Color planes (must be 1)
    uint16_t bit_count;        // Bits per pixel (24 or 32)
    uint32_t compression;      // Compression type (0 = BI_RGB)
    uint32_t size_image;       // Image size (can be 0 for BI_RGB)
    int32_t  x_pels_per_meter; // Horizontal resolution
    int32_t  y_pels_per_meter; // Vertical resolution
    uint32_t clr_used;         // Colors used (0 = all)
    uint32_t clr_important;    // Important colors (0 = all)
};
#pragma pack(pop)

/**
 * @brief High-performance native C++ BMP decoder
 *
 * Optimizations:
 * - No external dependencies (pure C++)
 * - Zero-copy for compatible formats
 * - SIMD-friendly memory layout
 * - Minimal branching in hot loops
 *
 * Supports:
 * - 24-bit RGB (BGR byte order -> RGB conversion)
 * - 32-bit RGBA (BGRA byte order -> RGB conversion, alpha stripped)
 * - Top-down and bottom-up orientations
 *
 * Thread-safe when each thread has its own instance.
 */
class BMPDecoder {
public:
    /**
     * @brief Construct decoder with optional buffer pool
     */
    explicit BMPDecoder(BufferPool* pool = nullptr)
        : buffer_pool_(pool) {}

    /**
     * @brief Decode BMP data to RGB
     *
     * @param bmp_data Span of BMP file bytes
     * @param output Output buffer for RGB data (resized automatically)
     * @param width Output: image width
     * @param height Output: image height
     * @param channels Output: number of channels (always 3 for RGB)
     *
     * @throws std::runtime_error on decode failure
     */
    void decode(
        std::span<const uint8_t> bmp_data,
        std::vector<uint8_t>& output,
        int& width,
        int& height,
        int& channels
    ) {
        if (bmp_data.size() < sizeof(BMPFileHeader) + sizeof(BMPInfoHeader)) {
            throw std::runtime_error("BMP data too small");
        }

        // Parse headers
        BMPFileHeader file_header;
        BMPInfoHeader info_header;

        std::memcpy(&file_header, bmp_data.data(), sizeof(file_header));
        std::memcpy(&info_header, bmp_data.data() + sizeof(file_header), sizeof(info_header));

        // Validate magic bytes
        if (file_header.type != 0x4D42) {  // 'BM'
            throw std::runtime_error("Invalid BMP magic bytes");
        }

        // Validate header size
        if (info_header.size != 40) {
            throw std::runtime_error("Unsupported BMP header (expected 40-byte BITMAPINFOHEADER)");
        }

        // Support BI_RGB, BI_RLE8, BI_RLE4
        if (info_header.compression != 0 &&  // BI_RGB
            info_header.compression != 1 &&  // BI_RLE8
            info_header.compression != 2) {  // BI_RLE4
            throw std::runtime_error("Unsupported BMP compression type");
        }

        // Get dimensions
        width = info_header.width;
        height = std::abs(info_header.height);
        channels = 3;  // Always RGB output

        // Allocate output buffer
        size_t output_size = width * height * channels;
        output.resize(output_size);

        // Get pixel data start
        const uint8_t* pixel_data = bmp_data.data() + file_header.off_bits;
        size_t pixel_data_size = bmp_data.size() - file_header.off_bits;

        // Determine scan direction
        bool top_down = (info_header.height < 0);

        // Decode based on compression type
        if (info_header.compression == 0) {  // BI_RGB (uncompressed)
            if (info_header.bit_count != 24 && info_header.bit_count != 32) {
                throw std::runtime_error("Only 24-bit and 32-bit uncompressed BMP supported");
            }

            int bytes_per_pixel = info_header.bit_count / 8;
            int bmp_row_stride = ((width * bytes_per_pixel + 3) / 4) * 4;

            if (bytes_per_pixel == 3) {
                decode_24bit(pixel_data, output.data(), width, height, bmp_row_stride, top_down);
            } else {
                decode_32bit(pixel_data, output.data(), width, height, bmp_row_stride, top_down);
            }
        } else if (info_header.compression == 1) {  // BI_RLE8
            if (info_header.bit_count != 8) {
                throw std::runtime_error("RLE8 compression requires 8-bit BMP");
            }

            // Need color palette for 8-bit BMPs
            const uint8_t* palette = bmp_data.data() + sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
            decode_rle8(pixel_data, pixel_data_size, output.data(), width, height, palette, top_down);
        } else if (info_header.compression == 2) {  // BI_RLE4
            if (info_header.bit_count != 4) {
                throw std::runtime_error("RLE4 compression requires 4-bit BMP");
            }

            const uint8_t* palette = bmp_data.data() + sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
            decode_rle4(pixel_data, pixel_data_size, output.data(), width, height, palette, top_down);
        }
    }

    /**
     * @brief Decode BMP into Sample with pooled buffer
     */
    void decode_sample(Sample& sample) {
        if (sample.jpeg_data.empty()) {
            throw std::runtime_error("Sample has no image data");
        }

        // Get pooled buffer if available
        std::vector<uint8_t> buffer;
        if (buffer_pool_) {
            auto pooled = buffer_pool_->acquire();
            buffer = std::move(*pooled);
        }

        // Decode BMP
        decode(sample.jpeg_data, buffer, sample.width, sample.height, sample.channels);

        // Move buffer into sample
        sample.decoded_rgb = std::move(buffer);
    }

    /**
     * @brief Get decoder info
     */
    static std::string version_info() {
        return "Native C++ BMP decoder (24/32-bit uncompressed + RLE4/RLE8)";
    }

private:
    BufferPool* buffer_pool_;

    /**
     * @brief Decode RLE8 compressed BMP
     *
     * RLE8 encoding:
     * - Encoded mode: count (1 byte) + color_index (1 byte)
     * - Absolute mode: 0x00 + count (1 byte) + color_indices (count bytes) + padding
     * - End of line: 0x00 0x00
     * - End of bitmap: 0x00 0x01
     * - Delta: 0x00 0x02 + dx + dy
     */
    void decode_rle8(
        const uint8_t* src,
        size_t src_size,
        uint8_t* dst,
        int width,
        int height,
        const uint8_t* palette,
        bool top_down
    ) {
        int x = 0, y = 0;
        size_t offset = 0;

        while (offset < src_size && y < height) {
            if (offset + 1 >= src_size) break;

            uint8_t count = src[offset++];
            uint8_t command = src[offset++];

            if (count == 0) {
                // Escape codes
                switch (command) {
                    case 0:  // End of line
                        x = 0;
                        y++;
                        break;

                    case 1:  // End of bitmap
                        return;

                    case 2:  // Delta
                        if (offset + 1 >= src_size) return;
                        x += src[offset++];
                        y += src[offset++];
                        break;

                    default:  // Absolute mode
                        {
                            int num_pixels = command;
                            for (int i = 0; i < num_pixels && x < width; i++, x++) {
                                if (offset >= src_size) return;
                                uint8_t color_index = src[offset++];

                                // Get RGB from palette (palette is BGRA format)
                                int dst_y = top_down ? y : (height - 1 - y);
                                uint8_t* pixel = dst + (dst_y * width + x) * 3;

                                pixel[0] = palette[color_index * 4 + 2];  // R
                                pixel[1] = palette[color_index * 4 + 1];  // G
                                pixel[2] = palette[color_index * 4 + 0];  // B
                            }

                            // Pad to word boundary
                            if (num_pixels % 2 == 1) offset++;
                        }
                        break;
                }
            } else {
                // Encoded mode: repeat color 'count' times
                for (int i = 0; i < count && x < width; i++, x++) {
                    int dst_y = top_down ? y : (height - 1 - y);
                    uint8_t* pixel = dst + (dst_y * width + x) * 3;

                    pixel[0] = palette[command * 4 + 2];  // R
                    pixel[1] = palette[command * 4 + 1];  // G
                    pixel[2] = palette[command * 4 + 0];  // B
                }
            }
        }
    }

    /**
     * @brief Decode RLE4 compressed BMP
     *
     * RLE4 encoding (4-bit pixels, 2 pixels per byte):
     * - Encoded mode: count (1 byte) + two_colors (1 byte containing 2 4-bit indices)
     * - Absolute mode: similar to RLE8 but with 4-bit indices
     */
    void decode_rle4(
        const uint8_t* src,
        size_t src_size,
        uint8_t* dst,
        int width,
        int height,
        const uint8_t* palette,
        bool top_down
    ) {
        int x = 0, y = 0;
        size_t offset = 0;

        while (offset < src_size && y < height) {
            if (offset + 1 >= src_size) break;

            uint8_t count = src[offset++];
            uint8_t command = src[offset++];

            if (count == 0) {
                // Escape codes
                switch (command) {
                    case 0:  // End of line
                        x = 0;
                        y++;
                        break;

                    case 1:  // End of bitmap
                        return;

                    case 2:  // Delta
                        if (offset + 1 >= src_size) return;
                        x += src[offset++];
                        y += src[offset++];
                        break;

                    default:  // Absolute mode
                        {
                            int num_pixels = command;
                            int num_bytes = (num_pixels + 1) / 2;

                            for (int i = 0; i < num_pixels && x < width; i++, x++) {
                                if (offset + (i / 2) >= src_size) return;

                                uint8_t byte = src[offset + (i / 2)];
                                uint8_t color_index = (i % 2 == 0) ? (byte >> 4) : (byte & 0x0F);

                                int dst_y = top_down ? y : (height - 1 - y);
                                uint8_t* pixel = dst + (dst_y * width + x) * 3;

                                pixel[0] = palette[color_index * 4 + 2];  // R
                                pixel[1] = palette[color_index * 4 + 1];  // G
                                pixel[2] = palette[color_index * 4 + 0];  // B
                            }

                            offset += num_bytes;

                            // Pad to word boundary
                            if (((num_bytes) % 2) == 1) offset++;
                        }
                        break;
                }
            } else {
                // Encoded mode: alternate between two colors
                uint8_t color1 = (command >> 4) & 0x0F;
                uint8_t color2 = command & 0x0F;

                for (int i = 0; i < count && x < width; i++, x++) {
                    uint8_t color_index = (i % 2 == 0) ? color1 : color2;

                    int dst_y = top_down ? y : (height - 1 - y);
                    uint8_t* pixel = dst + (dst_y * width + x) * 3;

                    pixel[0] = palette[color_index * 4 + 2];  // R
                    pixel[1] = palette[color_index * 4 + 1];  // G
                    pixel[2] = palette[color_index * 4 + 0];  // B
                }
            }
        }
    }

    /**
     * @brief Decode 24-bit BGR to RGB
     */
    void decode_24bit(
        const uint8_t* src,
        uint8_t* dst,
        int width,
        int height,
        int src_stride,
        bool top_down
    ) {
        for (int y = 0; y < height; ++y) {
            // Calculate source row (BMP is bottom-up by default)
            int src_y = top_down ? y : (height - 1 - y);
            const uint8_t* src_row = src + (src_y * src_stride);

            // Calculate destination row (always top-down)
            uint8_t* dst_row = dst + (y * width * 3);

            // Convert BGR to RGB
            for (int x = 0; x < width; ++x) {
                dst_row[x * 3 + 0] = src_row[x * 3 + 2];  // R = B
                dst_row[x * 3 + 1] = src_row[x * 3 + 1];  // G = G
                dst_row[x * 3 + 2] = src_row[x * 3 + 0];  // B = R
            }
        }
    }

    /**
     * @brief Decode 32-bit BGRA to RGB
     */
    void decode_32bit(
        const uint8_t* src,
        uint8_t* dst,
        int width,
        int height,
        int src_stride,
        bool top_down
    ) {
        for (int y = 0; y < height; ++y) {
            // Calculate source row (BMP is bottom-up by default)
            int src_y = top_down ? y : (height - 1 - y);
            const uint8_t* src_row = src + (src_y * src_stride);

            // Calculate destination row (always top-down)
            uint8_t* dst_row = dst + (y * width * 3);

            // Convert BGRA to RGB (strip alpha)
            for (int x = 0; x < width; ++x) {
                dst_row[x * 3 + 0] = src_row[x * 4 + 2];  // R = B
                dst_row[x * 3 + 1] = src_row[x * 4 + 1];  // G = G
                dst_row[x * 3 + 2] = src_row[x * 4 + 0];  // B = R
                // Skip alpha (src_row[x * 4 + 3])
            }
        }
    }
};

} // namespace turboloader
