/***************************************************************************
*             __________               __   ___.
*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
*                     \/            \/     \/    \/            \/
* $Id$
*
* Bitmap graphics on player LCD!
*
* Copyright (C) 2005 Jens Arnold
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/

#include "plugin.h"

#include "playergfx.h"

/*** globals ***/

static int char_width;
static int char_height;
static int pixel_height;
static int pixel_width;
static unsigned long gfx_chars[8];
static unsigned char gfx_buffer[56];
static int drawmode = DRMODE_SOLID;

/*** Special functions ***/

/* library init */
bool pgfx_init(int cwidth, int cheight)
{
    int i;

    if (((unsigned) cwidth * (unsigned) cheight) > 8 || (unsigned) cheight > 2)
        return false;

    char_width = cwidth;
    char_height = cheight;
    pixel_height = 7 * char_height;
    pixel_width = 5 * char_width;

    for (i = 0; i < cwidth * cheight; i++)
    {
        if ((gfx_chars[i] = rb->lcd_get_locked_pattern()) == 0)
        {
            pgfx_release();
            return false;
        }
    }

    return true;
}

/* library deinit */
void pgfx_release(void)
{
    int i;
    
    for (i = 0; i < 8; i++)
        if (gfx_chars[i])
            rb->lcd_unlock_pattern(gfx_chars[i]);
}

/* place the display */
void pgfx_display(int cx, int cy)
{
    int i, j;
    int width = MIN(char_width, 11 - cx);
    int height = MIN(char_height, 2 - cy);

    for (i = 0; i < width; i++)
        for (j = 0; j < height; j++)
            rb->lcd_putc(cx + i, cy + j, gfx_chars[char_height * i + j]);
}

void pgfx_display_block(int cx, int cy, int x, int y)
{
    rb->lcd_putc(cx, cy, gfx_chars[char_height * x + y]);
}


/*** Update functions ***/

void pgfx_update(void)
{
    int i;

    for (i = 0; i < char_width * char_height; i++)
        rb->lcd_define_pattern(gfx_chars[i], gfx_buffer + 7 * i);
    
    rb->lcd_update();
}

/*** Parameter handling ***/

void pgfx_set_drawmode(int mode)
{
    drawmode = mode & (DRMODE_SOLID|DRMODE_INVERSEVID);
}

int pgfx_get_drawmode(void)
{
    return drawmode;
}

/*** Low-level drawing functions ***/

static void setpixel(int x, int y)
{
    gfx_buffer[pixel_height * (x/5) + y] |= 0x10 >> (x%5);
}

static void clearpixel(int x, int y)
{
    gfx_buffer[pixel_height * (x/5) + y] &= ~(0x10 >> (x%5));
}

static void flippixel(int x, int y)
{
    gfx_buffer[pixel_height * (x/5) + y] ^= 0x10 >> (x%5);
}

static void nopixel(int x, int y)
{
    (void)x;
    (void)y;
}

lcd_pixelfunc_type* pgfx_pixelfuncs[8] = {
    flippixel, nopixel, setpixel, setpixel,
    nopixel, clearpixel, nopixel, clearpixel
};
                               
static void flipblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address ^= (bits & mask);
}

static void bgblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address &= (bits | ~mask);
}

static void fgblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address |= (bits & mask);
}

static void solidblock(unsigned char *address, unsigned mask, unsigned bits)
{
    unsigned data = *(char *)address;
    
    bits    ^= data;
    *address = data ^ (bits & mask);
}

static void flipinvblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address ^= (~bits & mask);
}

static void bginvblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address &= ~(bits & mask);
}

static void fginvblock(unsigned char *address, unsigned mask, unsigned bits)
{
    *address |= (~bits & mask);
}

static void solidinvblock(unsigned char *address, unsigned mask, unsigned bits)
{
    unsigned data = *(char *)address;
    
    bits     = ~bits ^ data;
    *address = data ^ (bits & mask);
}

lcd_blockfunc_type* pgfx_blockfuncs[8] = {
    flipblock, bgblock, fgblock, solidblock,
    flipinvblock, bginvblock, fginvblock, solidinvblock
};

/*** Drawing functions ***/

/* Clear the whole display */
void pgfx_clear_display(void)
{
    unsigned bits = (drawmode & DRMODE_INVERSEVID) ? 0x1F : 0;

    rb->memset(gfx_buffer, bits, char_width * pixel_height);
}

/* Set a single pixel */
void pgfx_drawpixel(int x, int y)
{
    if (((unsigned)x < (unsigned)pixel_width) 
        && ((unsigned)y < (unsigned)pixel_height))
        pgfx_pixelfuncs[drawmode](x, y);
}

/* Draw a line */
void pgfx_drawline(int x1, int y1, int x2, int y2)
{
    int numpixels;
    int i;
    int deltax, deltay;
    int d, dinc1, dinc2;
    int x, xinc1, xinc2;
    int y, yinc1, yinc2;
    lcd_pixelfunc_type *pfunc = pgfx_pixelfuncs[drawmode];

    deltax = abs(x2 - x1);
    deltay = abs(y2 - y1);
    xinc2 = 1;
    yinc2 = 1;

    if (deltax >= deltay)
    {
        numpixels = deltax;
        d = 2 * deltay - deltax;
        dinc1 = deltay * 2;
        dinc2 = (deltay - deltax) * 2;
        xinc1 = 1;
        yinc1 = 0;
    }
    else
    {
        numpixels = deltay;
        d = 2 * deltax - deltay;
        dinc1 = deltax * 2;
        dinc2 = (deltax - deltay) * 2;
        xinc1 = 0;
        yinc1 = 1;
    }
    numpixels++; /* include endpoints */

    if (x1 > x2)
    {
        xinc1 = -xinc1;
        xinc2 = -xinc2;
    }

    if (y1 > y2)
    {
        yinc1 = -yinc1;
        yinc2 = -yinc2;
    }

    x = x1;
    y = y1;

    for (i = 0; i < numpixels; i++)
    {
        if (((unsigned)x < (unsigned)pixel_width)
            && ((unsigned)y < (unsigned)pixel_height))
            pfunc(x, y);

        if (d < 0)
        {
            d += dinc1;
            x += xinc1;
            y += yinc1;
        }
        else
        {
            d += dinc2;
            x += xinc2;
            y += yinc2;
        }
    }
}

/* Draw a horizontal line (optimised) */
void pgfx_hline(int x1, int x2, int y)
{
    int nx;
    unsigned char *dst;
    unsigned mask, mask_right;
    lcd_blockfunc_type *bfunc;

    /* direction flip */
    if (x2 < x1)
    {
        nx = x1;
        x1 = x2;
        x2 = nx;
    }

    /* nothing to draw? */
    if (((unsigned)y >= (unsigned)pixel_height) || (x1 >= pixel_width) 
        || (x2 < 0))
        return;  
    
    /* clipping */
    if (x1 < 0)
        x1 = 0;
    if (x2 >= pixel_width)
        x2 = pixel_width - 1;
        
    bfunc = pgfx_blockfuncs[drawmode];
    dst   = &gfx_buffer[pixel_height * (x1/5) + y];
    nx    = x2 - (x1 - (x1 % 5));
    mask  = 0x1F >> (x1 % 5);
    mask_right = 0x1F0 >> (nx % 5);

    for (; nx >= 5; nx -= 5)
    {
        bfunc(dst, mask, 0xFFu);
        dst += pixel_height;
        mask = 0x1F;
    }
    mask &= mask_right;
    bfunc(dst, mask, 0x1F);
}

/* Draw a vertical line (optimised) */
void pgfx_vline(int x, int y1, int y2)
{
    int y;
    unsigned char *dst, *dst_end;
    unsigned mask;
    lcd_blockfunc_type *bfunc;

    /* direction flip */
    if (y2 < y1)
    {
        y = y1;
        y1 = y2;
        y2 = y;
    }
    
    /* nothing to draw? */
    if (((unsigned)x >= (unsigned)pixel_width) || (y1 >= pixel_height) 
        || (y2 < 0))
        return;  
    
    /* clipping */
    if (y1 < 0)
        y1 = 0;
    if (y2 >= pixel_height)
        y2 = pixel_height - 1;
        
    bfunc = pgfx_blockfuncs[drawmode];
    dst   = &gfx_buffer[pixel_height * (x/5) + y1];
    mask  = 0x10 >> (x % 5);

    dst_end = dst + y2 - y1;
    do
        bfunc(dst++, mask, 0x1F);
    while (dst <= dst_end);
}

/* Draw a rectangular box */
void pgfx_drawrect(int x, int y, int width, int height)
{
    if ((width <= 0) || (height <= 0))
        return;

    int x2 = x + width - 1;
    int y2 = y + height - 1;

    pgfx_vline(x, y, y2);
    pgfx_vline(x2, y, y2);
    pgfx_hline(x, x2, y);
    pgfx_hline(x, x2, y2);
}

/* Fill a rectangular area */
void pgfx_fillrect(int x, int y, int width, int height)
{
    int nx;
    unsigned char *dst, *dst_end;
    unsigned mask, mask_right;
    lcd_blockfunc_type *bfunc;

    /* nothing to draw? */
    if ((width <= 0) || (height <= 0) || (x >= pixel_width)
        || (y >= pixel_height) || (x + width <= 0) || (y + height <= 0))
        return;

    /* clipping */
    if (x < 0)
    {
        width += x;
        x = 0;
    }
    if (y < 0)
    {
        height += y;
        y = 0;
    }
    if (x + width > pixel_width)
        width = pixel_width - x;
    if (y + height > pixel_height)
        height = pixel_height - y;
    
    bfunc = pgfx_blockfuncs[drawmode];
    dst   = &gfx_buffer[pixel_height * (x/5) + y];
    nx    = width - 1 + (x % 5);
    mask  = 0x1F >> (x % 5);
    mask_right = 0x1F0 >> (nx % 5);

    for (; nx >= 5; nx -= 5)
    {
        unsigned char *dst_col = dst;

        dst_end = dst_col + height;
        do
            bfunc(dst_col++, mask, 0x1F);
        while (dst_col < dst_end);

        dst += pixel_height;
        mask = 0x1F;
    }
    mask &= mask_right;

    dst_end = dst + height;
    do
        bfunc(dst++, mask, 0x1F);
    while (dst < dst_end);
}

/* About PlayerGFX internal bitmap format:
 *
 * A bitmap contains one bit for every pixel that defines if that pixel is
 * black (1) or white (0). Bits within a byte are arranged horizontally, 
 * MSB at the left.
 * The bytes are stored in row-major order, with byte 0 being top left,
 * byte 1 2nd from left etc. Each row of bytes defines one pixel row.
 *
 * This approximates the (even more strange) internal hardware format. */

/* Draw a partial bitmap. stride is given in pixels */
void pgfx_bitmap_part(const unsigned char *src, int src_x, int src_y,
                      int stride, int x, int y, int width, int height)
{
    int nx, shift;
    unsigned char *dst, *dst_end;
    unsigned mask, mask_right;
    lcd_blockfunc_type *bfunc;

    /* nothing to draw? */
    if ((width <= 0) || (height <= 0) || (x >= pixel_width) 
        || (y >= pixel_height) || (x + width <= 0) || (y + height <= 0))
        return;
        
    /* clipping */
    if (x < 0)
    {
        width += x;
        src_x -= x;
        x = 0;
    }
    if (y < 0)
    {
        height += y;
        src_y -= y;
        y = 0;
    }
    if (x + width > pixel_width)
        width = pixel_width - x;
    if (y + height > pixel_height)
        height = pixel_height - y;
        
    stride = (stride + 7) >> 3; /* convert to no. of bytes */

    src   += stride * src_y + (src_x >> 3); /* move starting point */  
    dst   = &gfx_buffer[pixel_height * (x/5) + y];
    shift = 3 + (x % 5) - (src_x & 7);
    nx    = width - 1 + (x % 5);

    bfunc  = pgfx_blockfuncs[drawmode];
    mask  = 0x1F >> (x % 5);
    mask_right = 0x1F0 >> (nx % 5);
    
    dst_end = dst + height;
    do
    {
        const unsigned char *src_row = src;
        unsigned char *dst_row = dst++;
        unsigned mask_row = mask;
        unsigned data = *src_row++;
        int extrabits = shift;

        for (x = nx; x >= 5; x -= 5)
        {
            if (extrabits < 0)
            {
                data = (data << 8) | *src_row++;
                extrabits += 8;
            }
            bfunc(dst_row, mask_row, data >> extrabits);
            extrabits -= 5;
            dst_row += pixel_height;
            mask_row = 0x1F;
        }
        if (extrabits < 0)
        {
            data = (data << 8) | *src_row;
            extrabits += 8;
        }
        bfunc(dst_row, mask_row & mask_right, data >> extrabits);

        src += stride;
    }
    while (dst < dst_end);
}

/* Draw a full bitmap */
void pgfx_bitmap(const unsigned char *src, int x, int y, int width, int height)
{
    pgfx_bitmap_part(src, 0, 0, width, x, y, width, height);
}
