/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 Frederic Dang Ngoc
 *
 * 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 "config.h"
#include "options.h"
#ifdef USE_GAMES

#include <sprintf.h>
#include <file.h>
#include "star.h"
#include "lcd.h"
#include "button.h"
#include "kernel.h"
#include "menu.h"

#ifdef SIMULATOR
#include <stdio.h>
#endif
#include <string.h>

/* file which contains the levels */
#define STAR_LEVELS_FILE "/star_levels.txt"

/* title of the game */
#define STAR_TITLE      "Star"

/* font used to display title */
#define STAR_TITLE_FONT  2

/* size of the game board */
#define STAR_WIDTH      16
#define STAR_HEIGHT      9

/* left and top margin */
#define STAR_OFFSET_X    8
#define STAR_OFFSET_Y    0

/* number of level */
#define STAR_LEVEL_COUNT 20

/* size of a tile */
#define STAR_TILE_SIZE   6

/* values of object in the board */
#define STAR_VOID       '.' 
#define STAR_WALL       '*' 
#define STAR_STAR       'o' 
#define STAR_BALL       'X' 
#define STAR_BLOCK      'x' 

/* sleep time between two frames */
#define STAR_SLEEP      1

/* value of ball and block control */
#define STAR_CONTROL_BALL  0
#define STAR_CONTROL_BLOCK 1

/* this arrays contains all the levels */
static unsigned char levels[STAR_LEVEL_COUNT][144];

/* position of the ball */
static int ball_x, ball_y;

/* position of the block */
static int block_x, block_y;

/* number of stars to get to finish the level */
static int star_count;

/* the object we control : ball or block */
static int control;

/* the current board */
static char board[STAR_HEIGHT][STAR_WIDTH];

/* bitmap of the wall */
static unsigned char wall_bmp[STAR_TILE_SIZE]
    = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};

/* bitmap of the star */
static unsigned char star_bmp[STAR_TILE_SIZE]
    = {0x00, 0x0c, 0x12, 0x12, 0x0c, 0x00};

/* bitmap of the ball */
static unsigned char ball_bmp[STAR_TILE_SIZE]
    = {0x00, 0x0c, 0x1e, 0x1a, 0x0c, 0x00};

/* bitmap of the block */
static unsigned char block_bmp[STAR_TILE_SIZE]
    = {0x00, 0x1e, 0x1e, 0x1e, 0x1e, 0x00};

/* bitmap of the arrow animation */
static unsigned char arrow_bmp[4][7] =
    {
        {0x7f, 0x7f, 0x3e, 0x3e, 0x1c, 0x1c, 0x08},
        {0x3e, 0x3e, 0x1c, 0x1c, 0x08, 0x08, 0x08},
        {0x1c, 0x1c, 0x1c, 0x1c, 0x08, 0x08, 0x08},
        {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}
    };

/* sequence of the bitmap arrow to follow to do one turn */
static unsigned char anim_arrow[8] = {0, 1, 2, 3, 2, 1, 0};

/* if the levels has already been loaded into memory */
static bool levels_loaded = false;

/**
 * Display the text centered at position y
 */
static void star_center_putsy(int y, char *string)
{
    int width, height;

    lcd_getstringsize(string, &width, &height);
    lcd_putsxy((LCD_WIDTH - width) / 2, y, string);
}

/**
 * Do a pretty transition from one level to another.
 */
static void star_transition_update()
{ 
    int center_x = LCD_WIDTH / 2;
    int lcd_demi_width = LCD_WIDTH / 2;
    int center_y = LCD_HEIGHT / 2;
    int x;
    int y = 0;
    int var_y = 0;

    for (x = 0 ; x < lcd_demi_width ; x++)
    {
        var_y += LCD_HEIGHT;
        if (var_y > LCD_WIDTH)
        {
            var_y -= LCD_WIDTH;
            y++;
        }
        lcd_update_rect(center_x - x, center_y - y,
                x * 2, y * 2);
        sleep(STAR_SLEEP);
    }
    lcd_update();
}

/**
 * Load all levels from file into memory.
 */
static int star_load_all_levels(void)
{
    unsigned char buf[4096];
    int y,x,l;
    int fd;
    int nbyte;
    unsigned char *ptr_buf;
    unsigned char *ptr_level;

    levels_loaded = true;

    fd = open(STAR_LEVELS_FILE, O_RDONLY);
    if (fd == -1)
    {
        lcd_putsxy(0, 0, "Levels file");
        lcd_putsxy(0, 12, STAR_LEVELS_FILE);
        lcd_putsxy(0, 24, "not found !");
        lcd_update();
        sleep(HZ * 5);
        return 0;
    }

    nbyte = read(fd, buf, 4096);
    ptr_buf = buf;

    for (l = 0 ; l < STAR_LEVEL_COUNT ; l++)
    {
        ptr_level = levels[l];
        for (y = 0 ; y < STAR_HEIGHT ; y++)
        {
            for (x = 0 ; x < STAR_WIDTH ; x++)
            {
                *(ptr_level++) = *(ptr_buf++);
            }
            ptr_buf++;
        }
        ptr_buf++;
    }

    close(fd);
    return 1;
}

/**
 * Convert a 2 decimal integer into a string.
 */
static void int_to_str2(int nb, char *str)
{
    str[0] = nb / 10 + '0';
    str[1] = nb % 10 + '0';
    str[2] = '\0';
}

/**
 * Load a level into board array.
 */
static void star_load_level(int current_level)
{
    int x, y;
    char str_tmp[3];
    char *ptr = levels[current_level];

    control = STAR_CONTROL_BALL;
    star_count = 0;

    lcd_clear_display();

    for (y = 0 ; y < STAR_HEIGHT ; y++)
    {
        for (x = 0 ; x < STAR_WIDTH ; x++)
        {
            board[y][x] = *ptr;
            switch (*ptr)
            {
                case STAR_VOID:
                    break;

                case STAR_WALL:
                    lcd_bitmap (wall_bmp,
                        STAR_OFFSET_X + x * STAR_TILE_SIZE,
                        STAR_OFFSET_Y + y * STAR_TILE_SIZE,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, false);

                    break;

                case STAR_STAR:
                    lcd_bitmap (star_bmp,
                        STAR_OFFSET_X + x * STAR_TILE_SIZE,
                        STAR_OFFSET_Y + y * STAR_TILE_SIZE,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, false);
                    star_count++;
                    break;

                case STAR_BALL:
                    ball_x = x;
                    ball_y = y;
                    lcd_bitmap (ball_bmp,
                        STAR_OFFSET_X + x * STAR_TILE_SIZE,
                        STAR_OFFSET_Y + y * STAR_TILE_SIZE,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, false);
                    break;


                case STAR_BLOCK:
                    block_x = x;
                    block_y = y;
                    lcd_bitmap (block_bmp,
                        STAR_OFFSET_X + x * STAR_TILE_SIZE,
                        STAR_OFFSET_Y + y * STAR_TILE_SIZE,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, false);
                    break;
            }

            ptr++;
        }
    }
    lcd_putsxy(0,56, "L:");
    int_to_str2(current_level, str_tmp);
    lcd_putsxy(15,56, str_tmp);

    lcd_bitmap(star_bmp, 45, 57, STAR_TILE_SIZE, STAR_TILE_SIZE, true);
    lcd_putsxy(50,56, ":");
    int_to_str2(star_count, str_tmp);
    lcd_putsxy(60,56, str_tmp);

    lcd_putsxy(90,56, "C:");
    lcd_bitmap (ball_bmp, 105, 57, STAR_TILE_SIZE, STAR_TILE_SIZE, true);

    star_transition_update();
}

/**
 * Run the game.
 */
static void star_run_game(void)
{
    int current_level = 0;
    int move_x = 0;
    int move_y = 0;
    int i;
    char str_tmp[3];

    star_load_level(current_level);

    while (1)
    {
        move_x = 0;
        move_y = 0;

        switch (button_get(true))
        {
            case BUTTON_OFF:
                return; 
                
            case BUTTON_LEFT:
                move_x = -1;
                break; 

            case BUTTON_RIGHT:
                move_x = 1;
                break; 

            case BUTTON_UP:
                move_y = -1;
                break; 

            case BUTTON_DOWN:
                move_y = 1;
                break; 

            case BUTTON_F1:
                if (current_level > 0)
                {
                    current_level--;
                    star_load_level(current_level);
                }
                continue; 

            case BUTTON_F2:
                star_load_level(current_level);
                continue; 

            case BUTTON_F3:
                if (current_level < STAR_LEVEL_COUNT - 1)
                {
                    current_level++;
                    star_load_level(current_level);
                }
                continue; 

            case BUTTON_PLAY:
            case BUTTON_ON:
                if (control == STAR_CONTROL_BALL)
                {
                    lcd_bitmap (block_bmp, 105, 57,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, true);
                    control = STAR_CONTROL_BLOCK;
                }
                else
                {
                    lcd_bitmap (ball_bmp, 105, 57,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, true);
                    control = STAR_CONTROL_BALL;
                }
                    lcd_update_rect (105, 57, STAR_TILE_SIZE, STAR_TILE_SIZE);
                continue;

            default:
                continue;
        }

        if (control == STAR_CONTROL_BALL)
        {
            board[ball_y][ball_x] = STAR_VOID;
            while ((board[ball_y + move_y][ball_x + move_x] == STAR_VOID
                    || board[ball_y + move_y][ball_x + move_x] == STAR_STAR))

            {
                for (i = 0 ; i < 7 ; i++)
                {
                    lcd_bitmap (ball_bmp,
                        STAR_OFFSET_X + ball_x * STAR_TILE_SIZE + move_x * i,
                        STAR_OFFSET_Y + ball_y * STAR_TILE_SIZE + move_y * i,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, true);

                    lcd_update_rect (
                        STAR_OFFSET_X + ball_x * STAR_TILE_SIZE + move_x * i,
                        STAR_OFFSET_Y + ball_y * STAR_TILE_SIZE + move_y * i,
                        STAR_TILE_SIZE, STAR_TILE_SIZE);
                    sleep(STAR_SLEEP);
                }
                ball_x += move_x;
                ball_y += move_y;

                if (board[ball_y][ball_x] == STAR_STAR)
                {
                    board[ball_y][ball_x] = STAR_VOID;
                    star_count--;

                    int_to_str2(star_count, str_tmp);
                    lcd_putsxy(60,56, str_tmp);
                    lcd_update_rect (60, 56, 30, 8);
                }
            }
            board[ball_y][ball_x] = STAR_BALL;
        }
        else
        {
            board[block_y][block_x] = STAR_VOID;
            while (board[block_y + move_y][block_x + move_x] == STAR_VOID)
            {
                for (i = 0 ; i < 7 ; i++)
                {
                    lcd_bitmap (block_bmp,
                        STAR_OFFSET_X + block_x * STAR_TILE_SIZE + move_x * i,
                        STAR_OFFSET_Y + block_y * STAR_TILE_SIZE + move_y * i,
                        STAR_TILE_SIZE, STAR_TILE_SIZE, true);

                    lcd_update_rect (
                        STAR_OFFSET_X + block_x * STAR_TILE_SIZE + move_x * i,
                        STAR_OFFSET_Y + block_y * STAR_TILE_SIZE + move_y * i,
                        STAR_TILE_SIZE, STAR_TILE_SIZE);

                    sleep(STAR_SLEEP);
                }
                block_x += move_x;
                block_y += move_y;
            }
            board[block_y][block_x] = STAR_BLOCK;
        }

        if (star_count == 0)
        {
            current_level++;
            if (current_level == STAR_LEVEL_COUNT)
            {
                lcd_clear_display();
                star_center_putsy(20, "Congratulation !");
                   lcd_update();
                sleep(HZ * 3);
                return;
            }

            star_load_level(current_level);
        }
    }
}

/**
 * Display keys informations.
 */
static void star_display_keys(void)
{
    int key;
    lcd_clear_display();
    star_center_putsy(1, "KEYS");
    lcd_putsxy(5, 22, "[ON]  Toggle Ctl.");
    lcd_putsxy(5, 30, "[OFF] Exit");
    lcd_putsxy(5, 38, "[F1]  Prev. level");
    lcd_putsxy(5, 46, "[F2]  Reset level");
    lcd_putsxy(5, 54, "[F3]  Next level");
    lcd_update();
    while ((key = button_get(true)) != BUTTON_PLAY && key != BUTTON_ON);
}

/**
 * Display informations about the game.
 */
static void star_display_info(void)
{
    int key;
    lcd_clear_display();
    star_center_putsy(1, "INFO");
    lcd_putsxy(5, 22, "Take all \"o\" ");
    lcd_putsxy(5, 30, "to go to the");
    lcd_putsxy(5, 38, "next level.");
    lcd_putsxy(5, 46, "You can toggle");
    lcd_putsxy(5, 54, "control with the");
    lcd_update();
    while ((key = button_get(true)) != BUTTON_PLAY && key != BUTTON_ON);

    lcd_clear_display();
    star_center_putsy(1, "INFO");
    lcd_putsxy(5, 22, "block to use it");
    lcd_putsxy(5, 30, "as a mobile wall.");
    lcd_putsxy(5, 38, "The block cannot");
    lcd_putsxy(5, 46, "take \"o\".");
    lcd_update();
    while ((key = button_get(true)) != BUTTON_PLAY && key != BUTTON_ON);
}

/**
 * Display the choice menu.
 */
static void star_menu(void)
{
    int move_y;
    int menu_y = 0;
    int i;
    bool refresh = true;
    char anim_state = 0;

    while (true)
    {
        move_y = 0;
        if (refresh)
        {
            lcd_clear_display();
            star_center_putsy(1, STAR_TITLE);
            lcd_putsxy(15, 30, "Start");
            lcd_putsxy(15, 38, "Information");
            lcd_putsxy(15, 46, "Keys");
            lcd_putsxy(15, 54, "Exit");

                lcd_update();
            refresh = false;
        }

        lcd_bitmap(arrow_bmp[anim_arrow[(anim_state & 0x38) >> 3]],
                2, 30 + menu_y * 8, 7, 8, true);
        lcd_update_rect (2, 30 + menu_y * 8 + move_y * i, 8, 8);
        sleep(STAR_SLEEP);
        anim_state++;

        switch (button_get(false))
        {
            case BUTTON_OFF:
                return; 
            case BUTTON_UP:
                if (menu_y > 0)
                    move_y = -1;
                break; 
            case BUTTON_DOWN:
                if (menu_y < 3)
                    move_y = 1;
                break; 

            case BUTTON_ON:
            case BUTTON_PLAY:
                refresh = true;
                switch (menu_y)
                {
                    case 0:
                        star_run_game();
                        break;
                    case 1:
                        star_display_info();
                        break;
                    case 2:
                        star_display_keys();
                        break;
                    case 3:
                        return;
                }
                break; 

            default:
                continue;
        }

        for (i = 0 ; i < 8 ; i++)
        {
            lcd_clearrect (2, 30, 7, 4 * 8);
            lcd_bitmap(arrow_bmp[anim_arrow[(anim_state & 0x38) >> 3]],
                2, 30 + menu_y * 8 + move_y * i, 7, 8, false);
            lcd_update_rect(2, 30, 8, 4 * 8);
            anim_state++;
            sleep(STAR_SLEEP);
        }
        menu_y += move_y;
    }
}

/**
 * Main entry point from the menu to start the game control.
 */
bool star(void)
{
    // if levels are already loaded, no need to reload them.
    if (!levels_loaded)
    {
        lcd_clear_display();
        star_center_putsy(20, "Loading levels...");
           lcd_update();
        if (!star_load_all_levels())
            return false;
    }

    // display choice menu.
    star_menu();
    return true;
}

#endif /* USE_GAMES */
