/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Pacbox - a Pacman Emulator for Rockbox
 *
 * Based on PIE - Pacman Instructional Emulator
 *
 * Copyright (c) 1997-2003,2004 Alessandro Scotti
 * http://www.ascotti.org/
 *
 * 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"
#include "arcade.h"
#include "pacbox.h"
#include "pacbox_lcd.h"
#include "lib/configfile.h"

PLUGIN_HEADER
PLUGIN_IRAM_DECLARE

struct plugin_api* rb;

struct pacman_settings {
    int difficulty;
    int numlives;
    int bonus;
    int ghostnames;
    int showfps;
};

static struct pacman_settings settings;
static struct pacman_settings old_settings;

#define SETTINGS_VERSION 1
#define SETTINGS_MIN_VERSION 1
#define SETTINGS_FILENAME "pacbox.cfg"

static char* difficulty_options[] = { "Normal", "Hard" };
static char* numlives_options[] = { "1", "2", "3", "5" };
static char* bonus_options[] = {"10000", "15000", "20000", "No Bonus"};
static char* ghostnames_options[] = {"Normal", "Alternate"};
static char* showfps_options[] = {"No", "Yes"};

static struct configdata config[] =
{
   {TYPE_ENUM, 0, 2, &settings.difficulty, "Difficulty", difficulty_options, NULL},
   {TYPE_ENUM, 0, 4, &settings.numlives, "Pacmen Per Game", numlives_options, NULL},
   {TYPE_ENUM, 0, 4, &settings.bonus, "Bonus", bonus_options, NULL},
   {TYPE_ENUM, 0, 2, &settings.ghostnames, "Ghost Names", ghostnames_options , NULL},
   {TYPE_ENUM, 0, 2, &settings.showfps, "Show FPS", showfps_options, NULL},
};

static bool loadFile( const char * name, unsigned char * buf, int len )
{
    char filename[MAX_PATH];

    rb->snprintf(filename,sizeof(filename),"/.rockbox/pacman/%s",name);

    int fd = rb->open( filename, O_RDONLY);

    if( fd < 0 ) {
        return false;
    }
    
    int n = rb->read( fd, buf, len);
    
    rb->close( fd );

    if( n != len ) {
        return false;
    }

    return true;
}

static bool loadROMS( void )
{
    bool romsLoaded = false;

    romsLoaded = loadFile( "pacman.6e", ram_,           0x1000) &&
                 loadFile( "pacman.6f", ram_+0x1000,    0x1000) &&
                 loadFile( "pacman.6h", ram_+0x2000,    0x1000) &&
                 loadFile( "pacman.6j", ram_+0x3000,    0x1000) &&
                 loadFile( "pacman.5e", charset_rom_,   0x1000) &&
                 loadFile( "pacman.5f", spriteset_rom_, 0x1000);

    if( romsLoaded ) {
        decodeROMs();
        reset_PacmanMachine();
    }

    return romsLoaded;
}

/* A buffer to render Pacman's 244x288 screen into */
static unsigned char video_buffer[ScreenWidth*ScreenHeight] __attribute__ ((aligned (16)));

static long start_time;
static long video_frames = 0;

static int dipDifficulty[] = { DipDifficulty_Normal, DipDifficulty_Hard };
static int dipLives[] = { DipLives_1, DipLives_2, DipLives_3, DipLives_5 };
static int dipBonus[] = { DipBonus_10000, DipBonus_15000, DipBonus_20000, 
                          DipBonus_None };
static int dipGhostNames[] = { DipGhostNames_Normal, DipGhostNames_Alternate };

static int settings_to_dip(struct pacman_settings settings)
{
    return ( DipPlay_OneCoinOneGame | 
             DipCabinet_Upright | 
             DipMode_Play |
             DipRackAdvance_Off |

             dipDifficulty[settings.difficulty] |
             dipLives[settings.numlives] |
             dipBonus[settings.bonus] |
             dipGhostNames[settings.ghostnames]
           );
}

static bool pacbox_menu(void)
{
    int m;
    int result;
    int menu_quit=0;
    int new_setting;
    bool need_restart = false;

    static const struct opt_items noyes[2] = {
        { "No", -1 },
        { "Yes", -1 },
    };

    static const struct opt_items difficulty_options[2] = {
        { "Normal", -1 },
        { "Harder", -1 },
    };

    static const struct opt_items numlives_options[4] = {
        { "1", -1 },
        { "2", -1 },
        { "3", -1 },
        { "5", -1 },
    };

    static const struct opt_items bonus_options[4] = {
        { "10000 points", -1 },
        { "15000 points", -1 },
        { "20000 points", -1 },
        { "No bonus", -1 },
    };

    static const struct opt_items ghostname_options[2] = {
        { "Normal", -1 },
        { "Alternate", -1 },
    };

    static const struct menu_item items[] = {
        { "Difficulty", NULL },
        { "Pacmen Per Game", NULL },
        { "Bonus Life", NULL },
        { "Ghost Names", NULL },
        { "Display FPS", NULL },
        { "Restart", NULL },
        { "Quit", NULL },
    };
    
    m = rb->menu_init(items, sizeof(items) / sizeof(*items),
                      NULL, NULL, NULL, NULL);

    rb->button_clear_queue();

    while (!menu_quit) {
        result=rb->menu_show(m);

        switch(result)
        {
            case 0:
                new_setting=settings.difficulty;
                rb->set_option("Difficulty", &new_setting, INT,
                               difficulty_options , 2, NULL);
                if (new_setting != settings.difficulty) {
                    settings.difficulty=new_setting;
                    need_restart=true;
                }
                break;
            case 1:
                new_setting=settings.numlives;
                rb->set_option("Pacmen Per Game", &new_setting, INT,
                               numlives_options , 4, NULL);
                if (new_setting != settings.numlives) {
                    settings.numlives=new_setting;
                    need_restart=true;
                }
                break;
            case 2:
                new_setting=settings.bonus;
                rb->set_option("Bonus Life", &new_setting, INT,
                               bonus_options , 4, NULL);
                if (new_setting != settings.bonus) {
                    settings.bonus=new_setting;
                    need_restart=true;
                }
                break;
            case 3:
                new_setting=settings.ghostnames;
                rb->set_option("Ghost Names", &new_setting, INT,
                               ghostname_options , 2, NULL);
                if (new_setting != settings.ghostnames) {
                    settings.ghostnames=new_setting;
                    need_restart=true;
                }
                break;
            case 4: /* Show FPS */
                rb->set_option("Display FPS",&settings.showfps,INT, 
                               noyes, 2, NULL);
                break;
            case 5: /* Restart */
                need_restart=true;
                menu_quit=1;
                break;
            default:
                menu_quit=1;
                break;
        }
    }

    rb->menu_exit(m);

    if (need_restart) {
        init_PacmanMachine(settings_to_dip(settings));
    }

    /* Possible results: 
         exit game
         restart game
         usb connected
    */
    return (result==6);
}


/*
    Runs the game engine for one frame.
*/
static int gameProc( void )
{
    int x;
    int fps;
    char str[80];
    int status;
    long end_time;
    int frame_counter = 0;
    int yield_counter = 0;

    while (1)
    {
        /* Run the machine for one frame (1/60th second) */
        run();

        frame_counter++;

        /* Check the button status */
        status = rb->button_status();

#ifdef HAS_BUTTON_HOLD
        if (rb->button_hold())
        status = PACMAN_MENU;
#endif

        if ((status & PACMAN_MENU) == PACMAN_MENU
#ifdef PACMAN_RC_MENU
            || status == PACMAN_RC_MENU
#endif
        ) {
            end_time = *rb->current_tick;
            x = pacbox_menu();
            rb->lcd_clear_display();
#ifdef HAVE_REMOTE_LCD
            rb->lcd_remote_clear_display();
            rb->lcd_remote_update();
#endif
            if (x == 1) { return 1; }
            start_time += *rb->current_tick-end_time;
        }

#ifdef PACMAN_HAS_REMOTE
        setDeviceMode( Joy1_Left, (status & PACMAN_LEFT || status == PACMAN_RC_LEFT) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Right, (status & PACMAN_RIGHT || status == PACMAN_RC_RIGHT) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Up, (status & PACMAN_UP || status == PACMAN_RC_UP) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Down, (status & PACMAN_DOWN || status == PACMAN_RC_DOWN) ? DeviceOn : DeviceOff);
        setDeviceMode( CoinSlot_1, (status & PACMAN_COIN || status == PACMAN_RC_COIN) ? DeviceOn : DeviceOff);
        setDeviceMode( Key_OnePlayer, (status & PACMAN_1UP || status == PACMAN_RC_1UP) ? DeviceOn : DeviceOff);
        setDeviceMode( Key_TwoPlayers, (status & PACMAN_2UP || status == PACMAN_RC_2UP) ? DeviceOn : DeviceOff);
#else
        setDeviceMode( Joy1_Left, (status & PACMAN_LEFT) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Right, (status & PACMAN_RIGHT) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Up, (status & PACMAN_UP) ? DeviceOn : DeviceOff);
        setDeviceMode( Joy1_Down, (status & PACMAN_DOWN) ? DeviceOn : DeviceOff);
        setDeviceMode( CoinSlot_1, (status & PACMAN_COIN) ? DeviceOn : DeviceOff);
        setDeviceMode( Key_OnePlayer, (status & PACMAN_1UP) ? DeviceOn : DeviceOff);
#ifdef PACMAN_2UP
        setDeviceMode( Key_TwoPlayers, (status & PACMAN_2UP) ? DeviceOn : DeviceOff);
#endif
#endif

        /* We only update the screen every third frame - Pacman's native 
           framerate is 60fps, so we are attempting to display 20fps */
        if (frame_counter == 60 / FPS) {

            frame_counter = 0;
            video_frames++;

            yield_counter ++;

            if (yield_counter == FPS) {
                yield_counter = 0;
                rb->yield ();
            }
 
            /* The following functions render the Pacman screen from the 
               contents of the video and color ram.  We first update the 
               background, and then draw the Sprites on top. 
            */

            renderBackground( video_buffer );
            renderSprites( video_buffer );

            blit_display(rb->lcd_framebuffer,video_buffer);

            if (settings.showfps) {
                fps = (video_frames*HZ*100) / (*rb->current_tick-start_time);
                rb->snprintf(str,sizeof(str),"%d.%02d / %d fps  ",
                                             fps/100,fps%100,FPS);
                rb->lcd_putsxy(0,0,str);
            }

            rb->lcd_update();

           /* Keep the framerate at Pacman's 60fps */
            end_time = start_time + (video_frames*HZ)/FPS;
            while (TIME_BEFORE(*rb->current_tick,end_time)) {
                 rb->sleep(1);
            }
        }
    }
    return 0;
}

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

    PLUGIN_IRAM_INIT(api)
    rb = api;

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(true);
#endif
    rb->lcd_set_backdrop(NULL);
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
    rb->lcd_clear_display();
    rb->lcd_update();

    /* Set the default settings */
    settings.difficulty = 0; /* Normal */
    settings.numlives = 2;   /* 3 lives */
    settings.bonus = 0;      /* 10000 points */
    settings.ghostnames = 0; /* Normal names */
    settings.showfps = 0;    /* Do not show FPS */

    configfile_init(rb);

    if (configfile_load(SETTINGS_FILENAME, config,
                        sizeof(config)/sizeof(*config),
                        SETTINGS_MIN_VERSION
                       ) < 0)
    {
        /* If the loading failed, save a new config file (as the disk is
           already spinning) */
        configfile_save(SETTINGS_FILENAME, config,
                        sizeof(config)/sizeof(*config),
                        SETTINGS_VERSION);
    }

    /* Keep a copy of the saved version of the settings - so we can check if 
       the settings have changed when we quit */
    old_settings = settings;

    /* Initialise the hardware */
    init_PacmanMachine(settings_to_dip(settings));

    /* Load the romset */
    if (loadROMS()) {
        start_time = *rb->current_tick-1;

        gameProc();

        /* Save the user settings if they have changed */
        if (rb->memcmp(&settings,&old_settings,sizeof(settings))!=0) {
            rb->splash(0,true,"Saving settings...");
            configfile_save(SETTINGS_FILENAME, config,
                            sizeof(config)/sizeof(*config),
                            SETTINGS_VERSION);
        }
    } else {
        rb->splash(HZ*2,true,"No ROMs in /.rockbox/pacman/");
    }

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(false);
#endif

    return PLUGIN_OK;
}
