 /***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Port of xrick, a Rick Dangerous clone, to Rockbox.
 * See http://www.bigorno.net/xrick/
 *
 * Copyright (C) 2008-2014 Pierluigi Vicinanza
 *
 * 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 "xrick/system/system.h"

#include "xrick/config.h"
#include "xrick/draw.h"
#include "xrick/game.h"
#include "xrick/data/img.h"
#include "xrick/debug.h"

#include "plugin.h"
#include "lib/helper.h"

/*
 * Global variables
 */
U8 *sysvid_fb = NULL; /* xRick generic 320x200 8bpp frame buffer */

/*
 * Local variables
 */
static fb_data palette[256] IBSS_ATTR;
static bool isVideoInitialised = false;
#ifndef HAVE_LCD_COLOR
#  include "lib/grey.h"
GREY_INFO_STRUCT_IRAM
static unsigned char greybuffer[LCD_HEIGHT * LCD_WIDTH] IBSS_ATTR; /* off screen buffer */
static unsigned char *gbuf;
#  if LCD_PIXELFORMAT == HORIZONTAL_PACKING
enum { GREYBUFSIZE = (((LCD_WIDTH+7)/8)*LCD_HEIGHT*16+200) };
#  else
enum { GREYBUFSIZE = (LCD_WIDTH*((LCD_HEIGHT+7)/8)*16+200) };
#  endif
#endif /* ndef HAVE_LCD_COLOR */

#ifdef HAVE_LCD_COLOR
static fb_data *lcd_fb = NULL;
#endif

#if (LCD_HEIGHT < SYSVID_HEIGHT)
enum { ROW_RESIZE_STEP = (LCD_HEIGHT << 16) / SYSVID_HEIGHT };

static bool rowsToSkip[SYSVID_HEIGHT];

/*
 *
 */
static void calculateRowsToSkip(void)
{
    U32 currentRow, prevResizedRow;

    prevResizedRow = 0;
    rowsToSkip[0] = false;

    for (currentRow = 1; currentRow < SYSVID_HEIGHT; ++currentRow)
    {
        U32 resizedRow = (currentRow * ROW_RESIZE_STEP) >> 16;
        if (resizedRow == prevResizedRow)
        {
            rowsToSkip[currentRow] = true;
        }
        prevResizedRow = resizedRow;
    }
}
#endif /* (LCD_HEIGHT < SYSVID_HEIGHT) */

#if (LCD_WIDTH < SYSVID_WIDTH)
enum { COLUMN_RESIZE_STEP = (LCD_WIDTH << 16) / (SYSVID_WIDTH + (DRAW_XYMAP_SCRLEFT*2)) };

static bool columnsToSkip[SYSVID_WIDTH + (DRAW_XYMAP_SCRLEFT*2)];

/*
 *
 */
static void calculateColumnsToSkip(void)
{
    U32 currentColumn, prevResizedColumn;

    prevResizedColumn = 0;
    columnsToSkip[0] = false;

    for (currentColumn = 1; currentColumn < (SYSVID_WIDTH + (DRAW_XYMAP_SCRLEFT*2)); ++currentColumn)
    {
        U32 resizedColumn = (currentColumn * COLUMN_RESIZE_STEP) >> 16;
        if (resizedColumn == prevResizedColumn)
        {
            columnsToSkip[currentColumn] = true;
        }
        prevResizedColumn = resizedColumn;
    }
}
#endif /* (LCD_WIDTH < SYSVID_WIDTH) */

/*
 *
 */
void sysvid_setPalette(img_color_t *pal, U16 n)
{
    U16 i;

    for (i = 0; i < n; i++)
    {
#ifdef HAVE_LCD_COLOR
#if (LCD_PIXELFORMAT == RGB888) || (LCD_PIXELFORMAT == XRGB8888)
        unsigned long x =  LCD_RGBPACK(pal[i].r, pal[i].g, pal[i].b);
        palette[i] = FB_SCALARPACK(x);
#else
        palette[i] = LCD_RGBPACK(pal[i].r, pal[i].g, pal[i].b);
#endif
#else
        palette[i] = ((3 * pal[i].r) + (6 * pal[i].g) + pal[i].b) / 10;
#endif
    }
}

/*
 *
 */
void sysvid_setGamePalette()
{
    sysvid_setPalette(game_colors, game_color_count);
}

/*
 * Update screen
 */
void sysvid_update(const rect_t *rects)
{
    unsigned sourceRow, sourceLastRow;
    unsigned sourceColumn, sourceLastColumn;
    unsigned resizedRow, resizedColumn;
    unsigned resizedWidth, resizedHeight;
    unsigned x, y;
    U8 *sourceBuf, *sourceTemp;
    fb_data *destBuf, *destTemp;

    if (!rects)
    {
        return;
    }

    while (rects)
    {
        sourceRow = rects->y;
        sourceLastRow = sourceRow + rects->height;
        sourceColumn = rects->x;
        sourceLastColumn = sourceColumn + rects->width;

#if (LCD_WIDTH < SYSVID_WIDTH)
        /* skip black borders */
        if (sourceColumn < -DRAW_XYMAP_SCRLEFT)
        {
            sourceColumn = -DRAW_XYMAP_SCRLEFT;
        }
        if (sourceLastColumn > (SYSVID_WIDTH + DRAW_XYMAP_SCRLEFT))
        {
            sourceLastColumn = SYSVID_WIDTH + DRAW_XYMAP_SCRLEFT;
        }

        /* skip unwanted columns */
        while (columnsToSkip[sourceColumn + DRAW_XYMAP_SCRLEFT] /* && sourceColumn < (SYSVID_WIDTH + DRAW_XYMAP_SCRLEFT) */)
        {
            ++sourceColumn;
        }

        resizedColumn = ((sourceColumn + DRAW_XYMAP_SCRLEFT) * COLUMN_RESIZE_STEP) >> 16;
        resizedWidth = 0;
#else
        resizedColumn = sourceColumn;
        resizedWidth = rects->width;
#endif /* (LCD_WIDTH < SYSVID_WIDTH) */

#if (LCD_HEIGHT < SYSVID_HEIGHT)
        /* skip unwanted rows */
        while (rowsToSkip[sourceRow] /* && sourceRow < SYSVID_HEIGHT */)
        {
            ++sourceRow;
        }

        resizedRow = (sourceRow * ROW_RESIZE_STEP) >> 16;
        resizedHeight = 0;
#else
        resizedRow = sourceRow;
        resizedHeight = rects->height;
#endif /* (LCD_HEIGHT < SYSVID_HEIGHT) */

        sourceBuf = sysvid_fb;
        sourceBuf += sourceColumn + sourceRow * SYSVID_WIDTH;

#ifdef HAVE_LCD_COLOR
        if(!lcd_fb)
        {
            struct viewport *vp_main = rb->lcd_set_viewport(NULL);
            lcd_fb = vp_main->buffer->fb_ptr;
        }
        destBuf = lcd_fb;
#else
        destBuf = (fb_data*) greybuffer;
#endif /* HAVE_LCD_COLOR */
        destBuf += resizedColumn + resizedRow * LCD_WIDTH;

#if (LCD_WIDTH < SYSVID_WIDTH)
        sourceColumn += DRAW_XYMAP_SCRLEFT;
        sourceLastColumn += DRAW_XYMAP_SCRLEFT;
#endif /* (LCD_WIDTH < SYSVID_WIDTH) */

        for (y = sourceRow; y < sourceLastRow; ++y)
        {
#if (LCD_HEIGHT < SYSVID_HEIGHT)
            if (rowsToSkip[y])
            {
                sourceBuf += SYSVID_WIDTH;
                continue;
            }

            ++resizedHeight;
#endif /* (LCD_HEIGHT < SYSVID_HEIGHT) */

            sourceTemp = sourceBuf;
            destTemp = destBuf;
            for (x = sourceColumn; x < sourceLastColumn; ++x)
            {
#if (LCD_WIDTH < SYSVID_WIDTH)
                if (columnsToSkip[x])
                {
                    ++sourceTemp;
                    continue;
                }

                if (y == sourceRow)
                {
                    ++resizedWidth;
                }
#endif /* (LCD_WIDTH < SYSVID_WIDTH) */

                *destTemp = palette[*sourceTemp];

                ++sourceTemp;
                ++destTemp;
            }

            sourceBuf += SYSVID_WIDTH;
            destBuf += LCD_WIDTH;
        }

#ifdef HAVE_LCD_COLOR
        IFDEBUG_VIDEO2(
            for (y = resizedRow; y < resizedRow + resizedHeight; ++y)
            {
                destBuf = lcd_fb + resizedColumn + y * LCD_WIDTH;
                *destBuf = palette[0x01];
                *(destBuf + resizedWidth - 1) = palette[0x01];
            }

            for (x = resizedColumn; x < resizedColumn + resizedWidth; ++x)
            {
                destBuf = rb->lcd_fb + x + resizedRow * LCD_WIDTH;
                *destBuf = palette[0x01];
                *(destBuf + (resizedHeight - 1) * LCD_WIDTH) = palette[0x01];
            }
        );

        rb->lcd_update_rect(resizedColumn, resizedRow, resizedWidth, resizedHeight);
#else
        grey_ub_gray_bitmap_part(greybuffer, resizedColumn, resizedRow, LCD_WIDTH, resizedColumn, resizedRow, resizedWidth, resizedHeight);
#endif /* HAVE_LCD_COLOR */

       rects = rects->next;
    }
}

/*
 * Clear screen
 * (077C)
 */
void sysvid_clear(void)
{
    rb->memset(sysvid_fb, 0, sizeof(U8) * SYSVID_WIDTH * SYSVID_HEIGHT);
}

/*
 * Initialise video
 */
bool sysvid_init()
{
    bool success;

    if (isVideoInitialised)
    {
        return true;
    }

    IFDEBUG_VIDEO(sys_printf("xrick/video: start\n"););

    success = false;
    do
    {
        /* allocate xRick generic frame buffer into memory */
        sysvid_fb = sysmem_push(sizeof(U8) * SYSVID_WIDTH * SYSVID_HEIGHT);
        if (!sysvid_fb)
        {
            sys_error("(video) unable to allocate frame buffer");
            break;
        }

#ifndef HAVE_LCD_COLOR
        gbuf = sysmem_push(GREYBUFSIZE);
        if (!gbuf)
        {
            sys_error("(video) unable to allocate buffer for greyscale functions");
            break;
        }

        if (!grey_init(gbuf, GREYBUFSIZE, GREY_ON_COP, LCD_WIDTH, LCD_HEIGHT, NULL))
        {
            sys_error("(video) not enough memory to initialise greyscale functions");
            break;
        }
#endif /* ndef HAVE_LCD_COLOR */

        success = true;
    } while (false);

    if (!success)
    {
#ifndef HAVE_LCD_COLOR
        sysmem_pop(gbuf);
#endif
        sysmem_pop(sysvid_fb);
        return false;
    }

#if (LCD_HEIGHT < SYSVID_HEIGHT)
    calculateRowsToSkip();
#endif
#if (LCD_WIDTH < SYSVID_WIDTH)
    calculateColumnsToSkip();
#endif

#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
#endif
    /* Turn off backlight timeout */
    backlight_ignore_timeout();

    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
    rb->lcd_clear_display();

#ifdef HAVE_LCD_COLOR
    rb->lcd_update();
#else
    /* switch on greyscale overlay */
    grey_show(true);
#endif /* HAVE_LCD_COLOR */

    isVideoInitialised = true;
    IFDEBUG_VIDEO(sys_printf("xrick/video: ready\n"););
    return true;
}

/*
 * Shutdown video
 */
void sysvid_shutdown(void)
{
    if (!isVideoInitialised)
    {
        return;
    }

#ifndef HAVE_LCD_COLOR
    grey_show(false);
    grey_release();

    sysmem_pop(gbuf);
#endif /* ndef HAVE_LCD_COLOR */
    sysmem_pop(sysvid_fb);

    /* Turn on backlight timeout (revert to settings) */
    backlight_use_settings();

    isVideoInitialised = false;
    IFDEBUG_VIDEO(sys_printf("xrick/video: stop\n"););
}

/* eof */
