/* ======================================================================== */
/*  Graphics Prescaler                                                      */
/*                                                                          */
/*  The graphics prescaler allows scaling up the 160x200 bitmap to some     */
/*  other size prior to final scaling to display resolution.  This is       */
/*  where we'd apply transforms such as Scale2X or a variant.               */
/* ======================================================================== */

#include "config.h"
#include "periph/periph.h"
#include "gfx.h"
#include "gfx_prescale.h"
#include "scale/scale2x.h"
#include "scale/scale3x.h"


typedef struct gfx_prescaler_typ_pvt_t
{
    int     orig_x, orig_y;
    uint_8 *intermediate;   // for scale4x only
} gfx_prescaler_typ_pvt_t;




/* ======================================================================== */
/*  Prescalers                                                              */
/* ======================================================================== */

/* ------------------------------------------------------------------------ */
/*  NULL prescaler                                                          */
/* ------------------------------------------------------------------------ */
LOCAL void *gfx_prescaler_null_init(int orig_x, int orig_y,
                                    int *RESTRICT new_x,
                                    int *RESTRICT new_y,
                                    int *RESTRICT need_inter_vid,
                                    gfx_dirtyrect_spec *RESTRICT dr_spec)
{
    *new_x          = orig_x;
    *new_y          = orig_y;
    *need_inter_vid = 0;

    dr_spec->active_first_x = 0;
    dr_spec->active_first_y = 4;
    dr_spec->active_last_x  = orig_x - 1;
    dr_spec->active_last_y  = orig_y - 5;

    dr_spec->x_step         = 8;
    dr_spec->y_step         = 16;

    dr_spec->pitch          = orig_x;

    dr_spec->bord_first_x   = 0;
    dr_spec->bord_first_y   = 0;
    dr_spec->bord_last_x    = orig_x - 1;
    dr_spec->bord_last_y    = orig_y - 1;

    return NULL;
}

LOCAL void gfx_prescaler_null(const uint_8 *RESTRICT src,
                                    uint_8 *RESTRICT dst,
                                    void *RESTRICT opaque)
{
    UNUSED(src);
    UNUSED(dst);
    UNUSED(opaque);
    return;
}

LOCAL void gfx_prescaler_null_dtor(void *u)
{
    UNUSED(u);
    return;
}

/* ------------------------------------------------------------------------ */
/*  SCALE2X                                                                 */
/* ------------------------------------------------------------------------ */
LOCAL void *gfx_prescaler_scale2x_init(int orig_x, int orig_y,
                                       int *RESTRICT new_x,
                                       int *RESTRICT new_y,
                                       int *RESTRICT need_inter_vid,
                                       gfx_dirtyrect_spec *RESTRICT dr_spec)
{
    gfx_prescaler_typ_pvt_t *pvt = CALLOC(gfx_prescaler_typ_pvt_t, 1);

    if (!pvt)
    {
        fprintf(stderr, "Out of memory in gfx_prescale\n");
        exit(1);
    }

    pvt->orig_x = orig_x;
    pvt->orig_y = orig_y;
    pvt->intermediate = NULL;

    *new_x = orig_x * 2;
    *new_y = orig_y * 2;
    *need_inter_vid = 1;

    dr_spec->active_first_x = 0;
    dr_spec->active_first_y = 8;
    dr_spec->active_last_x  = 2*orig_x - 1;
    dr_spec->active_last_y  = 2*orig_y - 9;

    dr_spec->x_step         = 16;
    dr_spec->y_step         = 32;

    dr_spec->pitch          = 2*orig_x;

    dr_spec->bord_first_x   = 0;
    dr_spec->bord_first_y   = 0;
    dr_spec->bord_last_x    = 2*orig_x - 1;
    dr_spec->bord_last_y    = 2*orig_y - 1;

    return pvt;
}

/* Factored out and reused by scale4x */
LOCAL void perform_scale2x_8(int orig_x,
                             int orig_y,
                             const uint_8 *RESTRICT src,
                                   uint_8 *RESTRICT dst)
{
    int src_pitch = orig_x,
        dst_pitch = orig_x * 2;
    const uint_8 *src_prev = src;
    const uint_8 *src_curr = src;
    const uint_8 *src_next = src + src_pitch;
    int y;

    scale2x_8_def((scale2x_uint8 *)dst, (scale2x_uint8 *)(dst + dst_pitch),
                  (const scale2x_uint8 *)src_prev,
                  (const scale2x_uint8 *)src_curr,
                  (const scale2x_uint8 *)src_next, src_pitch);

    for (y = 2 ; y < orig_y; y++)
    {
        dst      += dst_pitch * 2;
        src_prev  = src_curr;
        src_curr  = src_next;
        src_next += src_pitch;

        scale2x_8_def((scale2x_uint8 *)dst, (scale2x_uint8 *)(dst + dst_pitch),
                      (const scale2x_uint8 *)src_prev,
                      (const scale2x_uint8 *)src_curr,
                      (const scale2x_uint8 *)src_next, src_pitch);
    }

    dst     += dst_pitch * 2;
    src_prev = src_curr;
    src_curr = src_next;

    scale2x_8_def((scale2x_uint8 *)dst, (scale2x_uint8 *)(dst + dst_pitch),
                  (const scale2x_uint8 *)src_prev,
                  (const scale2x_uint8 *)src_curr,
                  (const scale2x_uint8 *)src_next, src_pitch);
}

LOCAL void  gfx_prescaler_scale2x(const uint_8 *RESTRICT src,
                                        uint_8 *RESTRICT dst,
                                          void *RESTRICT opaque)
{
    gfx_prescaler_typ_pvt_t *pvt = (gfx_prescaler_typ_pvt_t *)opaque;
    int orig_x = pvt->orig_x, orig_y = pvt->orig_y;

    perform_scale2x_8(orig_x, orig_y, src, dst);

    return;
}


/* ------------------------------------------------------------------------ */
/*  SCALE3X                                                                 */
/* ------------------------------------------------------------------------ */
LOCAL void *gfx_prescaler_scale3x_init(int orig_x, int orig_y,
                                       int *RESTRICT new_x,
                                       int *RESTRICT new_y,
                                       int *RESTRICT need_inter_vid,
                                       gfx_dirtyrect_spec *RESTRICT dr_spec)
{
    gfx_prescaler_typ_pvt_t *pvt = CALLOC(gfx_prescaler_typ_pvt_t, 1);

    if (!pvt)
    {
        fprintf(stderr, "Out of memory in gfx_prescale\n");
        exit(1);
    }

    pvt->orig_x = orig_x;
    pvt->orig_y = orig_y;
    pvt->intermediate = NULL;

    *new_x = orig_x * 3;
    *new_y = orig_y * 3;
    *need_inter_vid = 1;

    dr_spec->active_first_x = 0;
    dr_spec->active_first_y = 8;
    dr_spec->active_last_x  = 3*orig_x - 1;
    dr_spec->active_last_y  = 3*orig_y - 9;

    dr_spec->x_step         = 16;
    dr_spec->y_step         = 32;

    dr_spec->pitch          = 3*orig_x;

    dr_spec->bord_first_x   = 0;
    dr_spec->bord_first_y   = 0;
    dr_spec->bord_last_x    = 3*orig_x - 1;
    dr_spec->bord_last_y    = 3*orig_y - 1;

    return pvt;
}

LOCAL void  gfx_prescaler_scale3x(const uint_8 *RESTRICT src,
                                        uint_8 *RESTRICT dst,
                                          void *RESTRICT opaque)
{
    gfx_prescaler_typ_pvt_t *pvt = (gfx_prescaler_typ_pvt_t *)opaque;
    int orig_x    = pvt->orig_x,
        orig_y    = pvt->orig_y,
        src_pitch = orig_x,
        dst_pitch = orig_x * 3;
    const uint_8 *src_prev = src;
    const uint_8 *src_curr = src;
    const uint_8 *src_next = src + src_pitch;
    int y;

    scale3x_8_def((scale3x_uint8 *)(dst                ),
                  (scale3x_uint8 *)(dst + dst_pitch * 2),
                  (scale3x_uint8 *)(dst + dst_pitch * 3),
                  (const scale3x_uint8 *)src_prev,
                  (const scale3x_uint8 *)src_curr,
                  (const scale3x_uint8 *)src_next, src_pitch);

    for (y = 3 ; y < orig_y; y++)
    {
        dst      += dst_pitch * 3;
        src_prev  = src_curr;
        src_curr  = src_next;
        src_next += src_pitch;

        scale3x_8_def((scale3x_uint8 *)(dst                ),
                      (scale3x_uint8 *)(dst + dst_pitch    ),
                      (scale3x_uint8 *)(dst + dst_pitch * 2),
                      (const scale3x_uint8 *)src_prev,
                      (const scale3x_uint8 *)src_curr,
                      (const scale3x_uint8 *)src_next, src_pitch);
    }

    dst     += dst_pitch * 3;
    src_prev = src_curr;
    src_curr = src_next;

    scale3x_8_def((scale3x_uint8 *)(dst                ),
                  (scale3x_uint8 *)(dst + dst_pitch * 2),
                  (scale3x_uint8 *)(dst + dst_pitch * 3),
                  (const scale3x_uint8 *)src_prev,
                  (const scale3x_uint8 *)src_curr,
                  (const scale3x_uint8 *)src_next, src_pitch);

    return;
}

/* ------------------------------------------------------------------------ */
/*  SCALE4X                                                                 */
/* ------------------------------------------------------------------------ */
LOCAL void *gfx_prescaler_scale4x_init(int orig_x, int orig_y,
                                       int *RESTRICT new_x,
                                       int *RESTRICT new_y,
                                       int *RESTRICT need_inter_vid,
                                       gfx_dirtyrect_spec *RESTRICT dr_spec)
{
    gfx_prescaler_typ_pvt_t *pvt = CALLOC(gfx_prescaler_typ_pvt_t, 1);
    uint_8 *intermediate = CALLOC(uint_8, orig_x * 2 * orig_y * 2);

    if (!pvt || !intermediate)
    {
        fprintf(stderr, "Out of memory in gfx_prescale\n");
        exit(1);
    }

    pvt->orig_x       = orig_x;
    pvt->orig_y       = orig_y;
    pvt->intermediate = intermediate;

    *new_x = orig_x * 4;
    *new_y = orig_y * 4;
    *need_inter_vid = 1;

    dr_spec->active_first_x = 0;
    dr_spec->active_first_y = 8;
    dr_spec->active_last_x  = 4*orig_x - 1;
    dr_spec->active_last_y  = 4*orig_y - 9;

    dr_spec->x_step         = 16;
    dr_spec->y_step         = 32;

    dr_spec->pitch          = 4*orig_x;

    dr_spec->bord_first_x   = 0;
    dr_spec->bord_first_y   = 0;
    dr_spec->bord_last_x    = 4*orig_x - 1;
    dr_spec->bord_last_y    = 4*orig_y - 1;

    return pvt;
}

LOCAL void gfx_prescaler_scale4x(const uint_8 *RESTRICT src,
                                       uint_8 *RESTRICT dst,
                                         void *RESTRICT opaque)
{
    gfx_prescaler_typ_pvt_t *pvt = (gfx_prescaler_typ_pvt_t *)opaque;
    int orig_x = pvt->orig_x, orig_y = pvt->orig_y;
    uint_8 *mid = pvt->intermediate;

    perform_scale2x_8(orig_x,     orig_y,     src, mid);
    perform_scale2x_8(orig_x * 2, orig_y * 2, mid, dst);
}

/* ------------------------------------------------------------------------ */
/*  SCALEXX destructor                                                      */
/* ------------------------------------------------------------------------ */

LOCAL void gfx_prescaler_scalexx_dtor(void *opaque)
{
    gfx_prescaler_typ_pvt_t *pvt = (gfx_prescaler_typ_pvt_t *)opaque;

    if (pvt)
    {
        CONDFREE(pvt->intermediate);
        free(pvt);
    }

    return;
}

/* ------------------------------------------------------------------------ */
/*  Rotate-180 Prescaler                                                    */
/* ------------------------------------------------------------------------ */
LOCAL void *gfx_prescaler_rot180_init(int orig_x, int orig_y,
                                      int *RESTRICT new_x,
                                      int *RESTRICT new_y,
                                      int *RESTRICT need_inter_vid,
                                      gfx_dirtyrect_spec *RESTRICT dr_spec)
{
    gfx_prescaler_typ_pvt_t *pvt = CALLOC(gfx_prescaler_typ_pvt_t, 1);

    if (!pvt)
    {
        fprintf(stderr, "Out of memory in gfx_prescale\n");
        exit(1);
    }

    pvt->orig_x = orig_x;
    pvt->orig_y = orig_y;
    pvt->intermediate = NULL;

    *new_x = orig_x;
    *new_y = orig_y;
    *need_inter_vid = 1;

    dr_spec->active_first_x = 0;
    dr_spec->active_first_y = 4;
    dr_spec->active_last_x  = orig_x - 1;
    dr_spec->active_last_y  = orig_y - 5;

    dr_spec->x_step         = 8;
    dr_spec->y_step         = 16;

    dr_spec->pitch          = orig_x;

    dr_spec->bord_first_x   = 0;
    dr_spec->bord_first_y   = 0;
    dr_spec->bord_last_x    = orig_x - 1;
    dr_spec->bord_last_y    = orig_y - 1;

    return pvt;
}

LOCAL void gfx_prescaler_rot180(const uint_8 *RESTRICT src,
                                      uint_8 *RESTRICT dst,
                                      void *RESTRICT opaque)
{
    gfx_prescaler_typ_pvt_t *pvt = (gfx_prescaler_typ_pvt_t *)opaque;
    const uint_8 *RESTRICT ps = src;
    uint_8       *RESTRICT pd = dst + pvt->orig_x * pvt->orig_y - 1;
    int i = pvt->orig_x * pvt->orig_y;

    while (i-- > 0)
        *pd-- = *ps++;

    return;
}

LOCAL void gfx_prescaler_rot180_dtor(void *u)
{
    UNUSED(u);
    return;
}

/* ======================================================================== */
/*  Prescaler Registry                                                      */
/* ======================================================================== */
gfx_prescaler_registry_t gfx_prescaler_registry[] =
{
    {   "None",     gfx_prescaler_null,
                    gfx_prescaler_null_init,
                    gfx_prescaler_null_dtor
    },

    {   "Scale2x",  gfx_prescaler_scale2x,
                    gfx_prescaler_scale2x_init,
                    gfx_prescaler_scalexx_dtor
    },

    {   "Scale3x",  gfx_prescaler_scale3x,
                    gfx_prescaler_scale3x_init,
                    gfx_prescaler_scalexx_dtor
    },

    {   "Scale4x",  gfx_prescaler_scale4x,
                    gfx_prescaler_scale4x_init,
                    gfx_prescaler_scalexx_dtor
    },

    {   "Rot180",   gfx_prescaler_rot180,
                    gfx_prescaler_rot180_init,
                    gfx_prescaler_rot180_dtor
    },
};


int gfx_prescaler_registry_size = sizeof(gfx_prescaler_registry) /
                                  sizeof(gfx_prescaler_registry_t);

