/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: rockblox.c,v 1.3 2005/06/24 22:33:13 amiconn Exp $
 *
 * Copyright (C) 1999 Mattis Wadman (nappe@sudac.org)
 *
 * Heavily modified for embedded use by Björn Stenberg (bjorn@haxx.se)
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"

#ifdef HAVE_LCD_BITMAP

static const int start_x = 5;
static const int start_y = 5;
#if defined(LCD_WIDTH) && (LCD_WIDTH==160)
#define block_width 	6			// old 4
#define block_height 	6			// old 3
#else
#define block_width 	4
#define block_height 	3
#endif
static const int max_x = block_height * 18;
static const int max_y = block_width * 10;
static const int text_y = block_height * 10 + 10;
static const short level_speeds[10] = {
    1000, 900, 800, 700, 600, 500, 400, 300, 250, 200
};
static const int blocks = 7;
static const int block_frames[7] = {1,2,2,2,4,4,4};

static int current_x, current_y, current_f, current_b;
static int level, score;
static int next_b, next_f;
static short lines;
static char virtual[LCD_WIDTH * LCD_HEIGHT];
static struct plugin_api* rb;

/*
 block_data is built up the following way

 first array index specifies the block number
 second array index specifies the rotation of the block
 third array index specifies:
     0: x-coordinates of pixels
     1: y-coordinates of pixels
 fourth array index specifies the coordinate of a pixel

 each block consists of four pixels whose relative coordinates are given
 with block_data
*/

static const char block_data[7][4][2][4] =
{
    {
        {{1,2,1,2},{1,1,2,2}}		// block
    },
    {
        {{0,1,1,2},{2,2,1,1}},		// z
        {{0,0,1,1},{0,1,1,2}}
    },
    {
        {{0,1,1,2},{1,1,2,2}},		// z
        {{1,1,0,0},{0,1,1,2}}
    },
    {
        {{0,1,2,3},{2,2,2,2}},		// line
        {{1,1,1,1},{0,1,2,3}}
    },
    {
        {{0,1,2,2},{1,1,1,2}},		// L
        {{1,1,1,2},{2,1,0,0}},
        {{0,0,1,2},{0,1,1,1}},
        {{0,1,1,1},{2,2,1,0}}
    },
    {
        {{0,0,1,2},{2,1,1,1}},		// L
        {{1,1,1,2},{0,1,2,2}},
        {{0,1,2,2},{1,1,1,0}},
        {{0,1,1,1},{0,0,1,2}},
    },
    {
        {{1,0,1,2},{0,1,1,1}},		// T
        {{2,1,1,1},{1,0,1,2}},
        {{1,0,1,2},{2,1,1,1}},
        {{0,1,1,1},{1,0,1,2}}
    }
};

static int t_rand(int range)
{
    return *rb->current_tick % range;
}

static void draw_frame(int fstart_x,int fstop_x,int fstart_y,int fstop_y)
{
    rb->lcd_drawline(fstart_x, fstart_y, fstop_x, fstart_y);
    rb->lcd_drawline(fstart_x, fstop_y, fstop_x, fstop_y);

    rb->lcd_drawline(fstart_x, fstart_y, fstart_x, fstop_y);
    rb->lcd_drawline(fstop_x, fstart_y, fstop_x, fstop_y);

    rb->lcd_drawline(fstart_x - 1, fstart_y + 1, fstart_x - 1, fstop_y + 1);
    rb->lcd_drawline(fstart_x - 1, fstop_y + 1, fstop_x - 1, fstop_y + 1);
}

static void draw_block(int x, int y, int block, int frame, bool clear)
{
    int i, a, b;

    for(i=0;i < 4;i++) {
        if (clear)
        {
            rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
            for (a = 0; a < block_height; a++)
                for (b = 0; b < block_width; b++)
                    rb->lcd_drawpixel(start_x + x + block_data[block][frame][1][i] * block_width - b,
                                   start_y + y + block_data[block][frame][0][i] * block_height + a);
            rb->lcd_set_drawmode(DRMODE_SOLID);
        }
        else
        {
            for (a = 0; a < block_height; a++)
                for (b = 0; b < block_width; b++)
                    rb->lcd_drawpixel(start_x+x+block_data[block][frame][1][i] * block_width - b,
                                  start_y+y+block_data[block][frame][0][i] * block_height + a);
        }
    }
}

static void to_virtual(void)
{
    int i, a, b;

    for(i = 0; i < 4; i++)
        for (a = 0; a < block_height; a++)
            for (b = 0; b < block_width; b++)
                *(virtual +
                (current_y + block_data[current_b][current_f][0][i] *
                  block_height + a) *
                  max_x + current_x + block_data[current_b][current_f][1][i] *
                  block_width - b) = current_b + 1;
}

static bool block_touch (int x, int y)
{
    int a,b;
    for (a = 0; a < block_width; a++)
        for (b = 0; b < block_height; b++)
            if (*(virtual + (y + b) * max_x + (x - a)) != 0)
                return true;
    return false;
}

static bool gameover(void)
{
    int i;
    int frame, block, y, x;

    x = current_x;
    y = current_y;
    block = current_b;
    frame = current_f;

    for(i = 0; i < 4; i++){
        /* Do we have blocks touching? */
        if(block_touch(x + block_data[block][frame][1][i] * block_width,
                       y + block_data[block][frame][0][i] * block_height))
        {
            /* Are we at the top of the frame? */
            if(x + block_data[block][frame][1][i] * block_width >= max_x - 4 * block_width)
            {
                /* Game over ;) */
                return true;
            }
        }
    }
    return false;
}

static bool valid_position(int x, int y, int block, int frame)
{
    int i;
    for(i=0;i < 4;i++)
        if ((y + block_data[block][frame][0][i] * block_height > max_y - block_height) ||
            (x + block_data[block][frame][1][i] * block_width > max_x - block_width) ||
            (y + block_data[block][frame][0][i] * block_height < 0) ||
            (x + block_data[block][frame][1][i] * block_width < block_width) ||
            block_touch (x + block_data[block][frame][1][i] * block_width,
                         y + block_data[block][frame][0][i] * block_height))
        {
            return false;
        }
    return true;
}

static void from_virtual(void)
{
    int x,y;

    for(y = 0; y < max_y; y++)
        for(x = 1; x < max_x - 1; x++)
            if(*(virtual + (y * max_x) + x) != 0)
            {
                rb->lcd_drawpixel(start_x + x, start_y + y);
            }
            else
            {
                rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
                rb->lcd_drawpixel(start_x + x, start_y + y);
                rb->lcd_set_drawmode(DRMODE_SOLID);
            }
}

static void move_block(int x,int y,int f)
{
    int last_frame = current_f;
    if(f != 0)
    {
        current_f += f;
        if(current_f > block_frames[current_b]-1)
            current_f = 0;
        if(current_f < 0)
            current_f = block_frames[current_b]-1;
    }

    if(valid_position(current_x + x, current_y + y, current_b, current_f))
    {
        draw_block(current_x,current_y,current_b,last_frame,true);
        current_x += x;
        current_y += y;
        draw_block(current_x,current_y,current_b,current_f,false);
        rb->lcd_update();
    }
    else
        current_f = last_frame;
}

static void new_block(void)
{
    current_b = next_b;
    current_f = next_f;
    current_x = max_x - block_width * 3;
    current_y = (int)block_height * 3;
    // debug //    next_b = (next_b + 1) % blocks;
    next_b = t_rand(blocks);
    next_f = 0;					// tetris always puts it in the basic position

	int tx = 7;

	int ps_x = 4 * block_width + tx + 4;
	int ps_y = 4 * block_height + 4;

	rb->lcd_drawline (max_x + ps_x, start_y - 1, max_x + tx, start_y - 1);
	rb->lcd_drawline (max_x + ps_x, start_y, max_x + ps_x, start_y + ps_y - 1);

	rb->lcd_drawline (max_x + tx, start_y + ps_y - 1, max_x + tx, start_y - 1);
	rb->lcd_drawline (max_x + tx - 1, start_y + ps_y, max_x + tx - 1, start_y);

	rb->lcd_drawline (max_x + tx, start_y + ps_y - 1, max_x + ps_x, start_y + ps_y - 1);
	rb->lcd_drawline (max_x + tx - 1, start_y + ps_y, max_x + ps_x - 1, start_y + ps_y);


    draw_block(max_x + 9, start_y - 4, current_b, current_f, true);
    draw_block(max_x + 9, start_y - 4, next_b, next_f, false);

    if(!valid_position(current_x, current_y, current_b, current_f))
    {
        draw_block(current_x, current_y, current_b, current_f, false);
        rb->lcd_update();
    }
    else
        draw_block(current_x, current_y, current_b, current_f, false);
}

static int check_lines(void)
{
    int x,y,i,j;
    bool line;
    int lines = 0;
    for(x = 0; x < max_x; x++)
    {
        line = true;
        for(y = 0; y < max_y; y++)
        {
            if(*(virtual + y * max_x + x) == 0)
            {
                line = false;
                break;
            }
        }

        if(line)
        {
            lines++;
            /* move rows down */
            for(i = x; i < max_x - 1; i++)
                for (j = 0; j < max_y; j++)
		            *(virtual + j * max_x + i)=*(virtual + j * max_x + (i + 1));

            x--; /* re-check this line */
        }
    }

    return lines / 4;
}

static void move_down(void)
{
    int l;
    char s[25];

    if(!valid_position(current_x - block_width, current_y, current_b, current_f))
    {
        to_virtual();
        l = check_lines();
        if(l)
        {
            lines += l;
            level = (int)lines/10;
            if(level > 9)
                level = 9;
            from_virtual();
            score += l*l;
        }

        rb->snprintf(s, sizeof(s), "%d Rows - Level %d", lines, level);
        rb->lcd_putsxy(2, text_y, s);

        new_block();
        move_block(0,0,0);
    }
    else
        move_block(-block_width,0,0);
}

static int game_loop(void)
{
    int button;

    while(1)
    {
        int count = 0;
        while(count * 300 < level_speeds[level])
        {
            button = rb->button_get_w_tmo(HZ/10);
            switch(button)
            {
                case BUTTON_OFF:
                    return PLUGIN_OK;

                case BUTTON_UP:
                case BUTTON_UP | BUTTON_REPEAT:
                    move_block(0,-block_height,0);
                    break;

                case BUTTON_DOWN:
                case BUTTON_DOWN | BUTTON_REPEAT:
                    move_block(0,block_height,0);
                    break;

                case BUTTON_RIGHT:
                case BUTTON_RIGHT | BUTTON_REPEAT:
                    move_block(0,0,1);
                    break;

                case BUTTON_LEFT:
                case BUTTON_LEFT | BUTTON_REPEAT:
                    move_down();
                    break;

                default:
                    if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
                        return PLUGIN_USB_CONNECTED;
                    break;
            }

            count++;
        }

        if(gameover())
        {
            rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
            rb->lcd_fillrect(0, text_y + 10, LCD_WIDTH, LCD_HEIGHT - text_y - 10);
            rb->lcd_set_drawmode(DRMODE_SOLID);
            rb->lcd_putsxy(2, text_y + 10, "You lose!");
            rb->lcd_update();
            rb->sleep(HZ * 3);
            return false;
        }

        move_down();
    }

    return false;
}

static void init_rockblox(void)
{
    rb->memset(&virtual, 0, sizeof(virtual));

    current_x = 0;
    current_y = 0;
    current_f = 0;
    current_b = 0;
    level = 0;
    lines = 0;
    score = 0;
    next_b = 0;
    next_f = 0;
}

enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
    int ret;

    TEST_PLUGIN_API(api);

    (void)parameter;
    rb = api;

    /* Lets use the default font */
    rb->lcd_setfont(FONT_SYSFIXED);

    init_rockblox();

    draw_frame(start_x, start_x + max_x - 1, start_y - 1, start_y + max_y);
    rb->lcd_putsxy(2, text_y, "0 Rows - Level 0");
    rb->lcd_update();

    next_b = t_rand(blocks);
    next_f = t_rand(block_frames[next_b]);
    new_block();
    ret = game_loop();

    rb->lcd_setfont(FONT_UI);

    return ret;
}

#endif


