// Licensed under GPL
// 03-06-05,2:57AM PST

#include "plugin.h"

#define BITMAP_PATH     "/.rockbox/rbounce.bitmaps"

#define CELL_SIZE       8

#define LEVEL_WIDTH     16
#define LEVEL_HEIGHT    16

#undef GRAYSCALE

#ifdef GRAYSCALE
    #define PPB         4
#else
    #define PPB         8
#endif

struct Bitmap
{
    char                *name;
    unsigned char       namelen;
    
    unsigned char       width;
    unsigned char       height;
    
    char               *data;
    int                 datalen;
};

static struct plugin_api* rb;

static struct Bitmap *bitmaps = NULL;
static int bitmapcount = 0;

static void *allocptr = NULL;
static int allocsize = 0;

void *alloc(int size)
{
    void *newptr;
    
    if (size < 0)
    {
        return NULL;
    }
    
    if (allocptr == NULL)
    {
        allocsize = 0;
        allocptr = rb->plugin_get_buffer(&allocsize);
    }
    
    if (size > allocsize)
    {
        return NULL;
    }
    
    newptr = allocptr;
    allocptr += size;
    allocsize -= size;
    
    return newptr;
}

/******************************************************************************
 * BITMAP                                                                     *
 ******************************************************************************/

int bitmapLoad(void)
{
    int handle, count, i;
    
    handle = rb->open(BITMAP_PATH, O_RDONLY);
    
    if (handle < 0)
    {
        return -1;
    }
    
    count = 0;
    rb->read(handle, &count, sizeof(int));
    
    if (count < 1)
    {
        rb->close(handle);
        return -2;
    }
    
    bitmapcount = count;
    bitmaps = (struct Bitmap *)alloc(count * sizeof(struct Bitmap));
    
    if (bitmaps == NULL)
    {
        rb->close(handle);
        return -3;
    }
    
    for (i = 0; i < count; i++)
    {
        rb->splash(0, true, "Loading bitmap %d/%d...", i + 1, count);
        
        // Read in name & length
        bitmaps[i].namelen = 0;
        rb->read(handle, &bitmaps[i].namelen, sizeof(unsigned char));
        bitmaps[i].name = (char *)alloc(bitmaps[i].namelen + 1);
        rb->read(handle, bitmaps[i].name, bitmaps[i].namelen);
        
        // Read in width & height
        bitmaps[i].width = 0;
        bitmaps[i].height = 0;
        rb->read(handle, &bitmaps[i].width, sizeof(unsigned char));
        rb->read(handle, &bitmaps[i].height, sizeof(unsigned char));
        
        // Read in bitmap data
        bitmaps[i].datalen = 0;
        rb->read(handle, &bitmaps[i].datalen, sizeof(int));
        bitmaps[i].data = (char *)alloc(bitmaps[i].datalen);
        rb->read(handle, bitmaps[i].data, bitmaps[i].datalen);
    }
    
    rb->close(handle);
    
    return 0;
}

bool bitmapExists(char *name)
{
    int i;
    
    if (bitmapcount == 0 || bitmaps == NULL || name == NULL)
    {
        return false;
    }
    
    for (i = 0; i < bitmapcount; i++)
    {
        if (rb->strcmp(bitmaps[i].name, name) == 0)
        {
            return true;
        }
    }
    
    return false;
}

struct Bitmap *bitmapFind(char *name)
{
    int i;
    
    if (bitmapcount == 0 || bitmaps == NULL || name == NULL)
    {
        return NULL;
    }
    
    for (i = 0; i < bitmapcount; i++)
    {
        if (rb->strcmp(bitmaps[i].name, name) == 0)
        {
            return &bitmaps[i];
        }
    }
    
    return NULL;
}

/******************************************************************************
 * SPLASH                                                                     *
 ******************************************************************************/

void splash_draw(void)
{
    struct Bitmap *bitmap;
    bitmap = bitmapFind("splash");
    rb->lcd_bitmap(bitmap->data, 0, 0, bitmap->width, bitmap->height, true);
}

/******************************************************************************
 * GAME                                                                       *
 ******************************************************************************/

struct Object
{
    struct
    {
        int x;
        int y;
    } position;
    
    struct
    {
        int x;
        int y;
    } delta;
    
    bool enabled;
};

struct Bitmap *game_bitmap_ball, *game_bitmap_cell, *game_bitmap_cursorv, *game_bitmap_cursorh;
static unsigned char game_board[LEVEL_WIDTH][LEVEL_HEIGHT];
static int game_level;
static int game_lives;
static struct Object game_balls[10];
struct Object cursor;
static int cursorMode;
static struct Object w1, w2;

void game_tile_draw(int x, int y);
void game_level_clear(void)
{
    int x, y;
    
    for (x = 0; x < LEVEL_WIDTH; x++)
    {
        for (y = 0; y < LEVEL_HEIGHT; y++)
        {
            game_board[x][y] = 0;
        }
    }
}

static int game_moving;

void game_level_reset(int level)
{
    int i;
    
    game_level_clear();
    game_level = level;
    game_lives = 1;
    game_moving = 0;
    
    w1.enabled = false;
    w2.enabled = false;
    
    cursorMode = 0;
    cursor.position.x = LEVEL_WIDTH / 2;
    cursor.position.y = LEVEL_HEIGHT / 2;
    cursor.delta.x = 0;
    cursor.delta.y = 0;
    
    if (game_level > 10)
    {
        game_level = 10;
    }
    
    for (i = 0; i < level; i++)
    {
        game_balls[i].position.x = (rb->rand() % LEVEL_WIDTH);
        game_balls[i].position.y = (rb->rand() % LEVEL_HEIGHT);
        game_balls[i].delta.x = (rb->rand() % 2) ? 1 : -1;
        game_balls[i].delta.y = (rb->rand() % 2) ? 1 : -1;
    }
}

void game_cursor_draw(void)
{
    struct Bitmap *bitmap;
    
    if (cursorMode)
    {
        bitmap = game_bitmap_cursorv;
    }
    else
    {
        bitmap = game_bitmap_cursorh;
    }
    
    rb->lcd_bitmap(bitmap->data, cursor.position.x * CELL_SIZE, cursor.position.y * CELL_SIZE, bitmap->width, bitmap->height, false);
}

void game_tile_draw(int x, int y)
{
    switch (game_board[x][y])
    {
        case 0:
        {
            rb->lcd_clearrect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
            break;
        }
        
        case 1:
        {
            rb->lcd_bitmap(game_bitmap_cell->data, x * CELL_SIZE, y * CELL_SIZE, game_bitmap_cell->width, game_bitmap_cell->height, true);
            break;
        }
        
        case 2:
        {
            rb->lcd_bitmap(game_bitmap_cell->data, x * CELL_SIZE, y * CELL_SIZE, game_bitmap_cell->width, game_bitmap_cell->height, true);
            rb->lcd_invertrect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
            break;
        }
    }
}

void game_wall_permanent(void)
{
    int x, y;
    
    for (x = 0; x < LEVEL_WIDTH; x++)
    {
        for (y = 0; y < LEVEL_HEIGHT; y++)
        {
            if (game_board[x][y] == 2)
            {
                game_board[x][y] = 1;
                game_tile_draw(x, y);
            }
        }
    }
}

void game_wall_move(void)
{
    if (game_moving)
    {
        if (w1.enabled == true)
        {
            if (game_board[w1.position.x][w1.position.y] == 1 || w1.position.x < 0 || w1.position.x >= LEVEL_WIDTH || w1.position.y < 0 || w1.position.y >= LEVEL_HEIGHT)
            {
                game_moving++;
                w1.delta.x = 0;
                w1.delta.y = 0;
                w1.enabled = false;
            }
            else
            {
                game_board[w1.position.x][w1.position.y] = 2;
                game_tile_draw(w1.position.x, w1.position.y);
                w1.position.x += w1.delta.x;
                w1.position.y += w1.delta.y;
            }
        }
        
        if (w2.enabled == true)
        {
            if (game_board[w2.position.x][w2.position.y] == 1 || w2.position.x < 0 || w2.position.x >= LEVEL_WIDTH || w2.position.y < 0 || w2.position.y >= LEVEL_HEIGHT)
            {
                game_moving++;
                w2.delta.x = 0;
                w2.delta.y = 0;
                w2.enabled = false;
            }
            else
            {
                game_board[w2.position.x][w2.position.y] = 2;
                game_tile_draw(w2.position.x, w2.position.y);
                w2.position.x += w2.delta.x;
                w2.position.y += w2.delta.y;
            }
        }
        
        if (game_moving > 2)
        {
            game_moving = 0;
            game_wall_permanent();
            //game_wall_fillholes();
            return;
        }
    }
}

void game_cursor_wall(void)
{
    if (!game_moving && game_board[cursor.position.x][cursor.position.y] != 1)
    {
        game_moving = 1;
        game_board[cursor.position.x][cursor.position.y] = 2;
        
        if (cursorMode == 0)
        {
            w1.position.x = cursor.position.x - 1;
            w1.position.y = cursor.position.y;
            w1.delta.x = -1;
            w1.delta.y = 0;
            w2.position.x = cursor.position.x + 1;
            w2.position.y = cursor.position.y;
            w2.delta.x = 1;
            w2.delta.y = 0;
            
            w1.enabled = w2.enabled = true;
        }
        else
        {
            w1.position.x = cursor.position.x;
            w1.position.y = cursor.position.y - 1;
            w1.delta.x = 0;
            w1.delta.y = -1;
            w2.position.x = cursor.position.x;
            w2.position.y = cursor.position.y + 1;
            w2.delta.x = 0;
            w2.delta.y = 1;
            
            w1.enabled = w2.enabled = true;
        }
    }
}

void game_cursor_move(void)
{
    if (cursor.delta.x != 0 || cursor.delta.y != 0)
    {
        game_tile_draw(cursor.position.x, cursor.position.y);
        
        cursor.position.x += cursor.delta.x;
        cursor.position.y += cursor.delta.y;
        
        if (cursor.position.x < 0)
        {
            cursor.position.x = 0;
        }
        else if (cursor.position.x >= LEVEL_WIDTH)
        {
            cursor.position.x = LEVEL_WIDTH - 1;
        }
        
        if (cursor.position.y < 0)
        {
            cursor.position.y = 0;
        }
        else if (cursor.position.y >= LEVEL_HEIGHT)
        {
            cursor.position.y = LEVEL_HEIGHT - 1;
        }
    }
    
    game_cursor_draw();
}

void game_balls_draw(void)
{
    int i;
    
    for (i = 0; i < game_level; i++)
    {
        rb->lcd_bitmap(game_bitmap_ball->data, game_balls[i].position.x * CELL_SIZE, game_balls[i].position.y * CELL_SIZE, game_bitmap_ball->width, game_bitmap_ball->height, true);
    }
}

int game_ball_collision(struct Object ball)
{
    ball.position.x += ball.delta.x;
    ball.position.y += ball.delta.y;
    
    if (ball.position.x >= LEVEL_WIDTH - 1 || ball.position.x < 0 || ball.position.y >= LEVEL_HEIGHT - 1 || ball.position.y < 0 || game_board[ball.position.x][ball.position.y] == 1)
    {
        return 1;
    }
    else if (game_board[ball.position.x][ball.position.y] == 2)
    {
        return 2;
    }
    
    return 0;
}

void game_level_draw(void)
{
    rb->lcd_fillrect(LEVEL_WIDTH * CELL_SIZE, 0, 2, LCD_HEIGHT);
}

void game_life_lost(void)
{
    int x, y;
    
    game_lives--;
    game_moving = 0;
    
    for (x = 0; x < LEVEL_WIDTH; x++)
    {
        for (y = 0; y < LEVEL_HEIGHT; y++)
        {
            if (game_board[x][y] == 2)
            {
                game_board[x][y] = 0;
                game_tile_draw(x, y);
            }
        }
    }
}

void game_balls_move(void)
{
    int i;
    bool boring;
    struct Object ball;
    
    for (i = 0; i < game_level; i++)
    {
        rb->lcd_clearrect(game_balls[i].position.x * CELL_SIZE, game_balls[i].position.y * CELL_SIZE, game_bitmap_ball->width, game_bitmap_ball->height);
        
        ball.position.x = game_balls[i].position.x;
        ball.position.y = game_balls[i].position.y;
        ball.delta.x    = game_balls[i].delta.x;
        ball.delta.y    = game_balls[i].delta.y;
        
        do
        {
            boring = false;
            
            if (game_ball_collision(ball) != 1)
            {
                break;
            }
            
            ball.delta.x = -ball.delta.x;
            
            if (game_ball_collision(ball) != 1)
            {
                break;
            }
            
            ball.delta.x = -ball.delta.x;
            ball.delta.y = -ball.delta.y;
            
            if (game_ball_collision(ball) != 1)
            {
                break;
            }
            
            ball.delta.x = -ball.delta.x;
            
            if (game_ball_collision(ball) != 1)
            {
                break;
            }
            
            boring = true;
        }
        while (false);
        
        if (boring == false)
        {
            game_balls[i].delta.x = ball.delta.x;
            game_balls[i].delta.y = ball.delta.y;
            game_balls[i].position.x += ball.delta.x;
            game_balls[i].position.y += ball.delta.y;
            
            if (game_board[ball.position.x][ball.position.y] == 2)
            {
                game_life_lost();
            }
        }
    }
    
    game_balls_draw();
}

void game_loop(void)
{
    int button;
    bool paused = false;
    
    game_bitmap_ball    = bitmapFind("ball");
    game_bitmap_cell    = bitmapFind("cell");
    game_bitmap_cursorv = bitmapFind("cursorv");
    game_bitmap_cursorh = bitmapFind("cursorh");
    
    rb->lcd_clear_display();
    
    game_level_reset(2);
    game_level_draw();
    game_balls_draw();
    
    while (true)
    {
        button = rb->button_get(false);
        
        if (button != BUTTON_NONE)
        {
            if (!(button & BUTTON_REL))
            {
                if (button & BUTTON_OFF)
                {
                    break;
                }
                else if (button & BUTTON_ON)
                {
                    paused = !paused;
                }
                else if (button & BUTTON_SELECT)
                {
                    game_cursor_wall();
                }
                else if (button & BUTTON_UP)
                {
                    cursorMode = 0;
                    cursor.delta.y = -1;
                }
                else if (button & BUTTON_DOWN)
                {
                    cursorMode = 0;
                    cursor.delta.y = 1;
                }
                else if (button & BUTTON_LEFT)
                {
                    cursorMode = 1;
                    cursor.delta.x = -1;
                }
                else if (button & BUTTON_RIGHT)
                {
                    cursorMode = 1;
                    cursor.delta.x = 1;
                }
            }
            else if (button & BUTTON_REL)
            {
                if (button & BUTTON_UP || button & BUTTON_DOWN)
                {
                    cursor.delta.y = 0;
                }
                else if (button & BUTTON_LEFT || button & BUTTON_RIGHT)
                {
                    cursor.delta.x = 0;
                }
            }
        }
        
        rb->yield();
        rb->sleep(6);
        
        if (paused)
        {
            continue;
        }
        
        game_wall_move();
        game_balls_move();
        game_cursor_move();
        rb->lcd_update();
    }
}

/******************************************************************************
 * MENU                                                                       *
 ******************************************************************************/

static const char *menuItems[] =
{
    "Begin",
    "Exit",
    NULL
};

#define MENU_START  0
#define MENU_EXIT   MENU_START + 1

static int menuWidth, menuHeight, menuSpacing;
static int menuX, menuY, menuCount;

void menu_draw(void)
{
    int i;
    int width, height;
    
    menuWidth = 0;
    menuSpacing = 0;
    
    for (i = 0; menuItems[i]; i++)
    {
        width = height = 0;
        rb->lcd_getstringsize(menuItems[i], &width, &height);
        
        if (width > menuWidth)
        {
            menuWidth = width;
        }
        if (height > menuSpacing)
        {
            menuSpacing = height;
        }
    }
    
    menuSpacing += 2;
    menuCount = i;
    menuHeight = menuSpacing * i;
    
    /*
    menuX = (LCD_WIDTH / 2) - (menuWidth / 2);
    menuY = LCD_HEIGHT - (menuHeight + menuSpacing);
    */
    
    menuX = LCD_WIDTH - (menuWidth + menuWidth / 4);
    menuY = (LCD_HEIGHT / 2) - (menuHeight / 2);
    
    rb->lcd_clearrect(menuX - 4, menuY - 4, menuWidth + 8, (menuCount * menuSpacing) + 8);
    rb->lcd_drawrect(menuX - 4, menuY - 4, menuWidth + 8, (menuCount * menuSpacing) + 8);
    
    for (i = 0; menuItems[i]; i++)
    {
        rb->lcd_putsxy(menuX, menuY + (i * menuSpacing), menuItems[i]);
    }
    
    rb->lcd_invertrect(menuX, menuY, menuWidth, menuSpacing);
    rb->lcd_update();
}

void menu_loop(void)
{
    int selected = 0;
    int button;
    
    menu_draw();
    
    while (true)
    {
        rb->button_clear_queue();
        button = rb->button_get(true);
        
        if (!(button & BUTTON_REL))
        {
            if (button & (BUTTON_UP | BUTTON_DOWN))
            {
                rb->lcd_invertrect(menuX, menuY + (selected * menuSpacing), menuWidth, menuSpacing);
                
                if (button & BUTTON_UP)
                {
                    selected--;
                    
                    if (selected < 0)
                    {
                        selected = menuCount - 1;
                    }
                }
                else if (button & BUTTON_DOWN)
                {
                    selected++;
                    
                    if (selected > menuCount - 1)
                    {
                        selected = 0;
                    }
                }
                rb->lcd_invertrect(menuX, menuY + (selected * menuSpacing), menuWidth, menuSpacing);
                rb->lcd_update();
            }
            else if (button & BUTTON_OFF || (button & (BUTTON_ON | BUTTON_SELECT) && selected == MENU_EXIT))
            {
                break;
            }
            else if (button & (BUTTON_ON | BUTTON_SELECT))
            {
                if (selected == MENU_START)
                {
                    game_loop();
                    splash_draw();
                    menu_draw();
                }
            }
        }
    }
}

enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
    int error;
    
    TEST_PLUGIN_API(api);
    (void)(parameter);
    rb = api;
    rb->srand(*rb->current_tick);
    
    error = bitmapLoad();
    
    if (error < 0)
    {
        rb->lcd_clear_display();
        switch (error)
        {
            case -1:
            {
                rb->splash(HZ * 2, true, "Failed to load bitmaps!");
                break;
            }
            
            case -2:
            {
                rb->splash(HZ * 2, true, "Bitmap has no images!");
                break;
            }
            
            case -3:
            {
                rb->splash(HZ * 2, true, "Not enough memory for bitmaps!");
                break;
            }
            
            case -4:
            {
                rb->splash(HZ * 2, true, "Incorrect bitmap size!");
                break;
            }
        }
        
        return PLUGIN_OK; //PLUGIN_ERROR;
    }
    
    if
    (
        bitmapExists("cursorh") == false ||
        bitmapExists("cursorv") == false ||
        bitmapExists("cell")    == false ||
        bitmapExists("splash")  == false
    )
    {
        rb->lcd_clear_display();
        rb->splash(HZ * 3, true, "Missing bitmap!");
        return PLUGIN_OK;//PLUGIN_ERROR;
    }
    
    rb->button_clear_queue();
    
    // Show splash image :D
    splash_draw();
    rb->lcd_update();
    
    rb->button_get_w_tmo(5 * HZ);
    rb->button_clear_queue();
    
    menu_loop();
    
    return PLUGIN_OK;
}
