/**
 * @file lv_qrcode.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_qrcode.h"
#if LV_USE_QRCODE

#include "qrcodegen.h"

/*********************
 *      DEFINES
 *********************/
#define QR_SIZE     140

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

/**********************
 *  STATIC PROTOTYPES
 **********************/

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

const lv_obj_class_t lv_qrcode_class = {
    .base_class = &lv_canvas_class
};

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

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/**
 * Create an empty QR code (an `lv_canvas`) object.
 * @param parent point to an object where to create the QR code
 * @param size width and height of the QR code
 * @param dark_color dark color of the QR code
 * @param light_color light color of the QR code
 * @return pointer to the created QR code object
 */
lv_obj_t * lv_qrcode_create(lv_obj_t * parent, lv_coord_t size, lv_color_t dark_color, lv_color_t light_color)
{
   uint32_t buf_size = LV_CANVAS_BUF_SIZE_INDEXED_1BIT(size, size);
   uint8_t * buf = lv_mem_alloc(buf_size);
   LV_ASSERT_MALLOC(buf);
   if(buf == NULL) return NULL;

   lv_obj_t * canvas = lv_canvas_create(parent);
   if(canvas == NULL) return NULL;

   lv_canvas_set_buffer(canvas, buf, size, size, LV_IMG_CF_INDEXED_1BIT);
   lv_canvas_set_palette(canvas, 0, dark_color);
   lv_canvas_set_palette(canvas, 1, light_color);

   return canvas;

}

/**
 * Set the data of a QR code object
 * @param qrcode pointer to aQ code object
 * @param data data to display
 * @param data_len length of data in bytes
 * @return LV_RES_OK: if no error; LV_RES_INV: on error
 */
lv_res_t lv_qrcode_update(lv_obj_t * qrcode, const void * data, uint32_t data_len)
{
    lv_color_t c;
    c.full = 1;
    lv_canvas_fill_bg(qrcode, c, LV_OPA_COVER);

    if(data_len > qrcodegen_BUFFER_LEN_MAX) return LV_RES_INV;

    uint8_t * qr0 = lv_mem_alloc(qrcodegen_BUFFER_LEN_MAX);
    LV_ASSERT_MALLOC(qr0);
    uint8_t * data_tmp = lv_mem_alloc(qrcodegen_BUFFER_LEN_MAX);
    LV_ASSERT_MALLOC(data_tmp);
    memcpy(data_tmp, data, data_len);

    bool ok = qrcodegen_encodeBinary(data_tmp, data_len,
            qr0, qrcodegen_Ecc_MEDIUM,
            qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX,
            qrcodegen_Mask_AUTO, true);

    if (!ok) {
        lv_mem_free(qr0);
        lv_mem_free(data_tmp);
        return LV_RES_INV;
    }


    lv_img_dsc_t * imgdsc = lv_canvas_get_img(qrcode);
    lv_coord_t obj_w = imgdsc->header.w;
    int qr_size = qrcodegen_getSize(qr0);
    int scale = obj_w / qr_size;
    int scaled = qr_size * scale;
    int margin = (obj_w - scaled) / 2;
    uint8_t * buf_u8 = (uint8_t *)imgdsc->data + 8;    /*+8 skip the palette*/

    /* Copy the qr code canvas:
     * A simple `lv_canvas_set_px` would work but it's slow for so many pixels.
     * So buffer 1 byte (8 px) from the qr code and set it in the canvas image */
    uint32_t row_byte_cnt = (imgdsc->header.w + 7) >> 3;
    int y;
    for (y = margin; y < scaled + margin; y+=scale) {
        uint8_t b = 0;
        uint8_t p = 0;
        bool aligned = false;
        int x;
        for (x = margin; x < scaled + margin; x++) {
            bool a = qrcodegen_getModule(qr0, (x - margin) / scale, (y - margin) / scale);

            if(aligned == false && (x & 0x7) == 0) aligned = true;

            if(aligned == false) {
                c.full = a ? 0 : 1;
                lv_canvas_set_px_color(qrcode, x, y, c);
            } else {
                if(!a) b |= (1 << (7 - p));
                p++;
                if(p == 8) {
                    uint32_t px = row_byte_cnt * y + (x >> 3);
                    buf_u8[px] = b;
                    b = 0;
                    p = 0;
                }
            }
        }

        /*Process the last byte of the row*/
        if(p) {
            /*Make the rest of the bits white*/
            b |= (1 << (8 - p)) - 1;

            uint32_t px = row_byte_cnt * y + (x >> 3);
            buf_u8[px] = b;
        }

      /*The Qr is probably scaled so simply to the repeated rows*/
      int s;
      const uint8_t * row_ori = buf_u8 + row_byte_cnt * y;
      for(s = 1; s < scale; s++) {
          memcpy((uint8_t*)buf_u8 + row_byte_cnt * (y + s), row_ori, row_byte_cnt);
      }
    }

    lv_mem_free(qr0);
    lv_mem_free(data_tmp);
    return LV_RES_OK;
}

/**
 * Delete a QR code object
 * @param qrcode pointer to a QR code obejct
 */
void lv_qrcode_delete(lv_obj_t * qrcode)
{
    lv_img_dsc_t * img = lv_canvas_get_img(qrcode);
    lv_img_cache_invalidate_src(img);
    lv_mem_free((void*)img->data);
    lv_obj_del(qrcode);
}

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

#endif /*LV_USE_QRCODE*/
