
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "serializers.h"
#include "ost_hal.h"
#include "debug.h"

#ifdef OST_USE_FF_LIBRARY
#include "ff.h"
#include "diskio.h"
typedef FIL file_t;
#else

// Use standard library
typedef FILE *file_t;
typedef int FRESULT;
#define F_OK
#endif

file_t ost_file_open(const char *filename)
{
#ifdef OST_USE_FF_LIBRARY
    file_t fil;
    FRESULT fr = f_open(&fil, filename, FA_READ);
    if (fr != FR_OK)
    {
        debug_printf("ERROR: f_open %d\n\r", (int)fr);
    }
    return fil;
#else
    return fopen(filename, "r");
#endif
}

static uint8_t bmpImage[512];

static uint8_t decompressed[1024];
static uint8_t palette[16 * 4];

typedef struct
{
    uint16_t type; /* Magic identifier            */
    uint32_t size; /* File size in bytes          */
    uint16_t reserved1;
    uint16_t reserved2;
    uint32_t offset; /* Offset to image data, bytes */
} bmp_header_t;

typedef struct
{
    uint32_t size; /* Header size in bytes      */
    uint32_t width;
    uint32_t height;      /* Width and height of image */
    uint16_t planes;      /* Number of colour planes   */
    uint16_t bits;        /* Bits per pixel            */
    uint32_t compression; /* Compression type          */
    uint32_t imagesize;   /* Image size in bytes       */
    uint32_t xresolution;
    uint32_t yresolution;      /* Pixels per meter          */
    uint32_t ncolours;         /* Number of colours         */
    uint32_t importantcolours; /* Important colours         */
    uint32_t rgb;
    uint32_t rgb2;
} bmp_infoheader_t;

static const uint32_t HEADER_SIZE = 14;
static const uint32_t INFO_HEADER_SIZE = 40;

uint8_t parse_bmp(const uint8_t *data, bmp_header_t *header, bmp_infoheader_t *info_header)
{
    uint8_t isBmp = 0;

    // Header is 14 bytes length
    isBmp = (data[0] == 'B') && (data[1] == 'M') ? 1 : 0;
    header->size = leu32_get(data + 2);
    header->offset = leu32_get(data + 10);

    info_header->size = leu32_get(data + HEADER_SIZE);
    info_header->width = leu32_get(data + HEADER_SIZE + 4);
    info_header->height = leu32_get(data + HEADER_SIZE + 8);
    info_header->planes = leu16_get(data + HEADER_SIZE + 12);
    info_header->bits = leu16_get(data + HEADER_SIZE + 14);
    info_header->compression = leu32_get(data + HEADER_SIZE + 16);
    info_header->imagesize = leu32_get(data + HEADER_SIZE + 20);
    info_header->xresolution = leu32_get(data + HEADER_SIZE + 24);
    info_header->yresolution = leu32_get(data + HEADER_SIZE + 28);
    info_header->ncolours = leu32_get(data + HEADER_SIZE + 32);
    info_header->importantcolours = leu32_get(data + HEADER_SIZE + 36);
    info_header->rgb = leu32_get(data + HEADER_SIZE + 40);
    info_header->rgb2 = leu32_get(data + HEADER_SIZE + 44);

    return isBmp;
}

void picture_show(const char *filename)
{
    file_t fil;
    uint32_t offset;
    unsigned int br;

    fil = ost_file_open(filename); //"0_000_314CBAA1.bmp");
    offset = 0;

#ifdef OST_USE_FF_LIBRARY
    f_lseek(&fil, offset);
    f_read(&fil, bmpImage, 512, &br);
#else
    fseek(fil, offset, SEEK_SET);
    fread(bmpImage, sizeof(uint8_t), 512, fil);
#endif
    int nblines = 0;

    bmp_header_t header;
    bmp_infoheader_t info_header;
    parse_bmp(bmpImage, &header, &info_header);

    // Compute some sizes
    uint32_t fileSize = header.size;
    // uint32_t compressedSize = fileSize - header.offset;
    uint32_t paletteSize = header.offset - (HEADER_SIZE + INFO_HEADER_SIZE);

    // Copy palette
    offset = HEADER_SIZE + INFO_HEADER_SIZE;
    memcpy(palette, bmpImage + HEADER_SIZE + INFO_HEADER_SIZE, paletteSize);

    offset = header.offset;
    uint8_t *compressed = &bmpImage[header.offset]; // start with data just after the BPM header

    debug_printf("File size (from header):%d\r\n", (uint32_t)header.size);
    debug_printf("File size (from data):%d\r\n", (uint32_t)fileSize);
    debug_printf("Data offset:%d\r\n", (uint32_t)header.offset);
    debug_printf("Image size:%d\r\n", (uint32_t)info_header.size);
    debug_printf("width:%d\r\n", (uint32_t)info_header.width);
    debug_printf("height:%d\r\n", (uint32_t)info_header.height);
    debug_printf("Planes:%d\r\n", (uint32_t)info_header.planes);
    debug_printf("Bits:%d\r\n", (uint32_t)info_header.bits);
    debug_printf("Compression:%d\r\n", (uint32_t)info_header.compression); // 2 - 4 bit run length encoding
    debug_printf("Image size:%d\r\n", (uint32_t)info_header.imagesize);
    debug_printf("X resolution:%d\r\n", (uint32_t)info_header.xresolution);
    debug_printf("Y resolution:%d\r\n", (uint32_t)info_header.yresolution);
    debug_printf("Colors:%d\r\n", (uint32_t)info_header.ncolours);
    debug_printf("Important colors:%d\r\n", (uint32_t)info_header.importantcolours);
    debug_printf("RGB :%d\r\n", (uint32_t)info_header.rgb);
    debug_printf("RGB2 :%d\r\n", (uint32_t)info_header.rgb2);

    // buffer de sortie, bitmap décompressé
    memset(decompressed, 0, sizeof(decompressed));

    //  btea((uint32_t*) bmpImage, -128, key);

    uint32_t pixel = 0; // specify the pixel offset
    bool end = false;
    uint32_t i = 0;
    uint32_t totalPixels = 0;
    rect_t pos;
    pos.x = 0;
    pos.y = 0;
    pos.width = info_header.width;
    pos.height = 1;
    do
    {
        // if we are behond the middle of the buffer, read more data from file
        if (i > 256)
        {
            offset = offset + i;

#ifdef OST_USE_FF_LIBRARY
            f_lseek(&fil, offset);
            f_read(&fil, bmpImage, 512, &br);
#else
            fseek(fil, offset, SEEK_SET);
            fread(bmpImage, sizeof(uint8_t), 512, fil);
#endif
            compressed = &bmpImage[0];
            i = 0;
        }

        uint8_t rleCmd = compressed[i];
        if (rleCmd > 0)
        {
            uint8_t val = compressed[i + 1];
            // repeat number of pixels
            for (uint32_t j = 0; j < rleCmd; j++)
            {
                if ((j & 1) == 0)
                {
                    decompressed[pixel] = (val & 0xF0) >> 4;
                }
                else
                {
                    decompressed[pixel] = (val & 0x0F);
                }
                pixel++;

                if (pixel > info_header.width)
                {
                    debug_printf("!"); // should be an error here
                }
            }
            i += 2; // jump pair instruction
        }
        else
        {
            uint8_t second = compressed[i + 1];
            if (second == 0)
            {
                if (pixel % info_header.width)
                {
                    // end of line
                    uint32_t lines = pixel / info_header.width;
                    uint32_t remaining = info_header.width - (pixel - (lines * info_header.width));

                    pixel += remaining;
                }
                i += 2;
            }
            else if (second == 1)
            {
                end = true;
            }
            else if (second == 2)
            {
                // delta N pixels and M lines
                pixel += compressed[i + 2] + compressed[i + 3] * info_header.width;
                i += 4;
            }
            else
            {
                // absolute mode
                uint8_t *ptr = &compressed[i + 2];
                // repeat number of pixels
                for (uint32_t j = 0; j < second; j++)
                {
                    if ((j & 1) == 0)
                    {
                        decompressed[pixel] = (*ptr & 0xF0) >> 4;
                    }
                    else
                    {
                        decompressed[pixel] = (*ptr & 0x0F);
                        ptr++;
                    }
                    pixel++;

                    if (pixel >= info_header.width)
                    {
                        debug_printf("!");
                        //  pixel = 0;
                    }
                }
                i += 2 + (second / 2);

                // padded in word boundary, jump if necessary
                if ((second / 2) % 2)
                {
                    i++;
                }
            }

            if (pixel == info_header.width)
            {
                // enough pixels to write a line to the screen
                ost_display_draw_h_line(pos.y, decompressed, palette);
                // debug_printf("POS Y: %d", pos.y);

                memset(decompressed, 0, sizeof(decompressed));
                // ili9341_write(&pos, decompressed);
                // next line...
                pos.y++;
                totalPixels += info_header.width;
                pixel = 0;
                nblines++;
            }
            else if (pixel > info_header.width)
            {
                debug_printf("!");
                //  pixel = 0;00000000000000000000000000000000000000
            }
        }

        if (totalPixels > (info_header.width * info_header.height))
        {
            end = true; // error
        }
    } while ((offset < fileSize) && !end);

    // if (end)
    // {
    //     debug_printf("\r\n>>>>> Decoding error !! <<<<\r\n");
    // }

    // Fill missing lines
    if (nblines < info_header.height)
    {
        memset(decompressed, 0, sizeof(decompressed));
        int missing_lines = (info_header.height - nblines);
        for (int i = 0; i < missing_lines; i++)
        {
            // ili9341_draw_h_line(pos.y, decompressed, palette);
            nblines++;
        }
    }

#ifdef OST_USE_FF_LIBRARY
    f_close(&fil);
#else
    fclose(fil);
#endif

    debug_printf("\r\nNb lines :%d\r\nTotal pixels: %d", (uint32_t)nblines, (uint32_t)totalPixels);
}
