/**
 * @file lv_bmp.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "../../draw/lv_image_decoder_private.h"
#include "../../../lvgl.h"
#if LV_USE_BMP

#include <string.h>
#include "../../core/lv_global.h"

/*********************
 *      DEFINES
 *********************/

#define DECODER_NAME    "BMP"

#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers)

/**********************
 *      TYPEDEFS
 **********************/

typedef struct {
    lv_fs_file_t f;
    unsigned int px_offset;
    int px_width;
    int px_height;
    unsigned int bpp;
    int row_size_bytes;
} bmp_dsc_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * src, lv_image_header_t * header);
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);

static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
                                    const lv_area_t * full_area, lv_area_t * decoded_area);

static void decoder_close(lv_image_decoder_t * dec, lv_image_decoder_dsc_t * dsc);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
void lv_bmp_init(void)
{
    lv_image_decoder_t * dec = lv_image_decoder_create();
    lv_image_decoder_set_info_cb(dec, decoder_info);
    lv_image_decoder_set_open_cb(dec, decoder_open);
    lv_image_decoder_set_get_area_cb(dec, decoder_get_area);
    lv_image_decoder_set_close_cb(dec, decoder_close);

    dec->name = DECODER_NAME;
}

void lv_bmp_deinit(void)
{
    lv_image_decoder_t * dec = NULL;
    while((dec = lv_image_decoder_get_next(dec)) != NULL) {
        if(dec->info_cb == decoder_info) {
            lv_image_decoder_delete(dec);
            break;
        }
    }
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/**
 * Get info about a BMP image
 * @param dsc image descriptor containing the source and type of the image and other info.
 * @param header store the info here
 * @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
 */
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header)
{
    LV_UNUSED(decoder);

    const void * src = dsc->src;
    lv_image_src_t src_type = dsc->src_type;          /*Get the source type*/

    /*If it's a BMP file...*/
    if(src_type == LV_IMAGE_SRC_FILE) {
        const char * fn = src;
        if(lv_strcmp(lv_fs_get_ext(fn), "bmp") == 0) {              /*Check the extension*/
            /*Save the data in the header*/
            uint8_t headers[54];

            lv_fs_read(&dsc->file, headers, 54, NULL);
            uint32_t w;
            uint32_t h;
            lv_memcpy(&w, headers + 18, 4);
            lv_memcpy(&h, headers + 22, 4);
            header->w = w;
            header->h = h;

            uint16_t bpp;
            lv_memcpy(&bpp, headers + 28, 2);
            switch(bpp) {
                case 16:
                    header->cf = LV_COLOR_FORMAT_RGB565;
                    break;
                case 24:
                    header->cf = LV_COLOR_FORMAT_RGB888;
                    break;
                case 32:
                    header->cf = LV_COLOR_FORMAT_ARGB8888;
                    break;
                default:
                    LV_LOG_WARN("Not supported bpp: %d", bpp);
                    return LV_RESULT_OK;
            }
            return LV_RESULT_OK;
        }
    }
    /* BMP file as data not supported for simplicity.
     * Convert them to LVGL compatible C arrays directly. */
    else if(src_type == LV_IMAGE_SRC_VARIABLE) {
        return LV_RESULT_INVALID;
    }

    return LV_RESULT_INVALID;         /*If didn't succeeded earlier then it's an error*/
}

/**
 * Open a BMP image and return the decided image
 * @param decoder pointer to the decoder
 * @param dsc     pointer to the decoder descriptor
 * @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
 */
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
    LV_UNUSED(decoder);

    /*If it's a BMP file...*/
    if(dsc->src_type == LV_IMAGE_SRC_FILE) {
        const char * fn = dsc->src;

        if(lv_strcmp(lv_fs_get_ext(fn), "bmp") != 0) {
            return LV_RESULT_INVALID;       /*Check the extension*/
        }

        bmp_dsc_t b;
        lv_memset(&b, 0x00, sizeof(b));

        lv_fs_res_t res = lv_fs_open(&b.f, dsc->src, LV_FS_MODE_RD);
        if(res != LV_FS_RES_OK) return LV_RESULT_INVALID;

        uint8_t header[54];
        lv_fs_read(&b.f, header, 54, NULL);

        if(0x42 != header[0] || 0x4d != header[1]) {
            lv_fs_close(&b.f);
            return LV_RESULT_INVALID;
        }

        lv_memcpy(&b.px_offset, header + 10, 4);
        lv_memcpy(&b.px_width, header + 18, 4);
        lv_memcpy(&b.px_height, header + 22, 4);
        lv_memcpy(&b.bpp, header + 28, 2);
        b.row_size_bytes = ((b.bpp * b.px_width + 31) / 32) * 4;

        dsc->user_data = lv_malloc(sizeof(bmp_dsc_t));
        LV_ASSERT_MALLOC(dsc->user_data);
        if(dsc->user_data == NULL) return LV_RESULT_INVALID;
        lv_memcpy(dsc->user_data, &b, sizeof(b));
        return LV_RESULT_OK;
    }
    /* BMP file as data not supported for simplicity.
     * Convert them to LVGL compatible C arrays directly. */
    else if(dsc->src_type == LV_IMAGE_SRC_VARIABLE) {
        return LV_RESULT_INVALID;
    }

    return LV_RESULT_INVALID;    /*If not returned earlier then it failed*/
}

static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
                                    const lv_area_t * full_area, lv_area_t * decoded_area)
{
    LV_UNUSED(decoder);
    bmp_dsc_t * b = dsc->user_data;
    lv_draw_buf_t * decoded = (void *)dsc->decoded;

    if(decoded_area->y1 == LV_COORD_MIN) {
        *decoded_area = *full_area;
        decoded_area->y2 = decoded_area->y1;
        int32_t w_px = lv_area_get_width(full_area);
        lv_draw_buf_t * reshaped = lv_draw_buf_reshape(decoded, dsc->header.cf, w_px, 1, LV_STRIDE_AUTO);
        if(reshaped == NULL) {
            if(decoded != NULL) {
                lv_draw_buf_destroy(decoded);
                decoded = NULL;
                dsc->decoded = NULL;
            }
            decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, w_px, 1, dsc->header.cf, LV_STRIDE_AUTO);
            if(decoded == NULL) return LV_RESULT_INVALID;
        }
        else {
            decoded = reshaped;
        }
        dsc->decoded = decoded;
    }
    else {
        decoded_area->y1++;
        decoded_area->y2++;
    }

    if(decoded_area->y1 > full_area->y2) {
        return LV_RESULT_INVALID;
    }
    else {
        int32_t y = (b->px_height - 1) - (decoded_area->y1); /*BMP images are stored upside down*/
        uint32_t p = b->px_offset + b->row_size_bytes * y;
        p += (decoded_area->x1) * (b->bpp / 8);
        lv_fs_seek(&b->f, p, LV_FS_SEEK_SET);
        uint32_t line_width_byte = lv_area_get_width(full_area) * (b->bpp / 8);
        lv_fs_read(&b->f, decoded->data, line_width_byte, NULL);

        return LV_RESULT_OK;
    }
}

/**
 * Free the allocated resources
 */
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
    LV_UNUSED(decoder);
    bmp_dsc_t * b = dsc->user_data;
    lv_fs_close(&b->f);
    lv_free(dsc->user_data);
    if(dsc->decoded) lv_draw_buf_destroy((void *)dsc->decoded);

}

#endif /*LV_USE_BMP*/
