#include "jpeg81.h"
#include "idct.h"
#include "GETC.h"
#include "rb_glue.h"

#include "../imageviewer.h"


/**************** begin Application ********************/

/************************* Types ***************************/

struct t_disp
{
    unsigned char* bitmap;
};

/************************* Globals ***************************/

/* decompressed image in the possible sizes (1,2,4,8), wasting the other */
static struct t_disp disp[9];

static struct JPEGD jpg; /* too large for stack */

/************************* Implementation ***************************/

static void draw_image_rect(struct image_info *info,
                            int x, int y, int width, int height)
{
    struct t_disp* pdisp = (struct t_disp*)info->data;
#ifdef HAVE_LCD_COLOR
    rb->lcd_bitmap_part(
        (fb_data*)pdisp->bitmap, info->x + x, info->y + y,
        STRIDE(SCREEN_MAIN, info->width, info->height),
        x + MAX(0, (LCD_WIDTH-info->width)/2),
        y + MAX(0, (LCD_HEIGHT-info->height)/2),
        width, height);
#else
    mylcd_ub_gray_bitmap_part(
            pdisp->bitmap, info->x + x, info->y + y, info->width,
            x + MAX(0, (LCD_WIDTH-info->width)/2),
            y + MAX(0, (LCD_HEIGHT-info->height)/2),
            width, height);
#endif
}

static int img_mem(int ds)
{
    struct JPEGD* j = &jpg;
    return j->Y/ds * j->X/ds*sizeof(fb_data);
}

/* my memory pool (from the mp3 buffer) */
static char print[32]; /* use a common snprintf() buffer */

static void scaled_dequantization_and_idct(void)
{
    struct JPEGD* j = &jpg;
    // The following code is based on RAINBOW lib jpeg2bmp example:
    // https://github.com/Halicery/vc_rainbow/blob/605c045a564dad8e2df84e48914eac3d2d8d4a9b/jpeg2bmp.c

    printf("Scaled de-quantization and IDCT.. ");
    int c, i, n;

    // Pre-scale quant-tables
    int SQ[4][64];
    for (c=0; c<4 && j->QT[c][0]; c++)
    {
        int *q= j->QT[c], *sq= SQ[c];
        for (i=0; i<64; i++) sq[i]= q[i] * SCALEM[zigzag[i]];
    }

    // DEQUANT + IDCT
    for (c=0; c<j->Nf; c++)
    {
        struct COMP *C= j->Components+c;
        //int *q= j->QT[C->Qi];
        int *sq= SQ[C->Qi];

        for (n=0; n < C->du_size; n++)
        {
            /*
            // <--- scaled idct
            int k, t[64];
            TCOEF *coef= du[x];
            t[0]= coef[0] * q[0] + 1024;							// dequant DC and level-shift (8-bit)
            for (k=1; k<64; k++) t[zigzag[k]] = coef[k] * q[k];		// dequant AC (+zigzag)
            idct_s(t, coef);
            */

            // <--- scaled idct with dequant
            idct_sq( C->du[ (n / C->du_w) * C->du_width +  n % C->du_w ], sq );
        }
    }
    printf("done\n");
}

static int load_image(char *filename, struct image_info *info,
                      unsigned char *buf, ssize_t *buf_size,
                      int offset, int filesize)
{
    (void)filesize;
    int status;
    struct JPEGD *p_jpg = &jpg;

    memset(&disp, 0, sizeof(disp));
    memset(&jpg, 0, sizeof(jpg));

    init_mem_pool(buf, *buf_size);

    if (!OPEN(filename))
    {
        return PLUGIN_ERROR;
    }
    if (offset)
    {
        POS(offset);
    }

    if (!iv->settings->hide_info &&
        !iv->running_slideshow)
    {
        rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1);
        rb->lcd_puts(0, 2, "decoding...");
        rb->lcd_update();
    }
    long time; /* measured ticks */

    /* the actual decoding */
    time = *rb->current_tick;
    status = JPEGDecode(p_jpg);
    time = *rb->current_tick - time;

    CLOSE();

    if (status < 0)
    {   /* bad format or minimum components not contained */
        if (status == JPEGENUMERR_MALLOC)
        {
            return PLUGIN_OUTOFMEM;
        }
        rb->splashf(HZ, "unsupported %d", status);
        return  PLUGIN_ERROR;
    }

    if (!iv->settings->hide_info &&
        !iv->running_slideshow)
    {
        rb->lcd_putsf(0, 2, "image %dx%d", p_jpg->X, p_jpg->Y);
        int w, h; /* used to center output */
        rb->snprintf(print, sizeof(print), "jpegp %ld.%02ld sec ", time/HZ, time%HZ);
        rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
        rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
        rb->lcd_update();
        //rb->sleep(100);
    }

    info->x_size = p_jpg->X;
    info->y_size = p_jpg->Y;

#ifdef DISK_SPINDOWN
    if (iv->running_slideshow && iv->immediate_ata_off)
    {
        /* running slideshow and time is long enough: power down disk */
        rb->storage_sleep();
    }
#endif

    if ( 3 != p_jpg->Nf )
        return PLUGIN_ERROR;

    scaled_dequantization_and_idct();

    *buf_size = freeze_mem_pool();
    return PLUGIN_OK;
}

static int get_image(struct image_info *info, int frame, int ds)
{
    (void)frame;
    struct JPEGD* p_jpg = &jpg;
    struct t_disp* p_disp = &disp[ds]; /* short cut */

    info->width = p_jpg->X / ds;
    info->height = p_jpg->Y / ds;
    info->data = p_disp;

    if (p_disp->bitmap != NULL)
    {
        /* we still have it */
        return PLUGIN_OK;
    }

    struct JPEGD* j = p_jpg;
    int mem = img_mem(ds);

    p_disp->bitmap = malloc(mem);

    if (!p_disp->bitmap)
    {
        clear_mem_pool();
        memset(&disp, 0, sizeof(disp));
        p_disp->bitmap = malloc(mem);
        if (!p_disp->bitmap)
            return PLUGIN_ERROR;
    }

    fb_data *bmp = (fb_data *)p_disp->bitmap;

    // The following code is based on RAINBOW lib jpeg2bmp example:
    // https://github.com/Halicery/vc_rainbow/blob/605c045a564dad8e2df84e48914eac3d2d8d4a9b/jpeg2bmp.c
    // Primitive yuv-rgb converter for all sub-sampling types, 24-bit BMP only
    printf("YUV-to-RGB conversion.. ");
    int h0 = j->Hmax / j->Components[0].Hi;
    int v0 = j->Vmax / j->Components[0].Vi;
    int h1 = j->Hmax / j->Components[1].Hi;
    int v1 = j->Vmax / j->Components[1].Vi;
    int h2 = j->Hmax / j->Components[2].Hi;
    int v2 = j->Vmax / j->Components[2].Vi;

    int x, y;
    int max_y = info->height * ds;
    int max_x = info->width * ds;
    for (y = 0; y < max_y; y += ds)
    {
        TCOEF *C0 =
                j->Components[0].du[j->Components[0].du_width * ((y / v0) / 8)] + 8 * ((y / v0) & 7);
        TCOEF *C1 =
                j->Components[1].du[j->Components[1].du_width * ((y / v1) / 8)] + 8 * ((y / v1) & 7);
        TCOEF *C2 =
                j->Components[2].du[j->Components[2].du_width * ((y / v2) / 8)] + 8 * ((y / v2) & 7);
        for (x = 0; x < max_x; x += ds)
        {
            int c0, c1, c2;
            if (ds == 1)
            {
                c0 = C0[(x / h0 / 8) * 64 + ((x / h0) & 7)];
                c1 = C1[(x / h1 / 8) * 64 + ((x / h1) & 7)];
                c2 = C2[(x / h2 / 8) * 64 + ((x / h2) & 7)];
            }
            else
            {
                /* Box filter downsampling (area averaging):
                   Average a ds*ds block of Y/U/V samples per output pixel
                   for simple anti-aliasing without extra buffers */
                int sumY = 0, sumU = 0, sumV = 0;
                int dx, dy;
                int area = ds * ds;
                for (dy = 0; dy < ds; dy++)
                {
                    TCOEF *C0_dy = j->Components[0].du[j->Components[0].du_width * (((y + dy) / v0) / 8)] + 8 * (((y + dy) / v0) & 7);
                    TCOEF *C1_dy = j->Components[1].du[j->Components[1].du_width * (((y + dy) / v1) / 8)] + 8 * (((y + dy) / v1) & 7);
                    TCOEF *C2_dy = j->Components[2].du[j->Components[2].du_width * (((y + dy) / v2) / 8)] + 8 * (((y + dy) / v2) & 7);
                    for (dx = 0; dx < ds; dx++)
                    {
                        int sx = x + dx;
                        sumY += C0_dy[((sx / h0 / 8) * 64) + ((sx / h0) & 7)];
                        sumU += C1_dy[((sx / h1 / 8) * 64) + ((sx / h1) & 7)];
                        sumV += C2_dy[((sx / h2 / 8) * 64) + ((sx / h2) & 7)];
                    }
                }
                c0 = (sumY + area/2) / area;
                c1 = (sumU + area/2) / area;
                c2 = (sumV + area/2) / area;
            }

            // ITU BT.601 full-range YUV-to-RGB integer approximation
            {
                int y = (c0 << 5) + 16;
                int u = c1 - 128;
                int v = c2 - 128;

                int b = CLIP[(y + 57 * u)>>5];			// B;
                int g = CLIP[(y - 11 * u - 23 * v)>>5];	// G
                int r = CLIP[(y + 45 * v)>>5];			// R;
                *bmp++= FB_RGBPACK(r,g,b);
            }
        }
    }
    printf("done\n");
    return 0;
}

const struct image_decoder image_decoder = {
    false,
    img_mem,
    load_image,
    get_image,
    draw_image_rect,
};

IMGDEC_HEADER
