/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Pacbox - a Pacman Emulator for Rockbox
 *
 * Based on PIE - Pacman Instructional Emulator
 *
 * Copyright (c) 1997-2003,2004 Alessandro Scotti
 * http://www.ascotti.org/
 * AI code (c) 2017 Moshe Piekarski
 *
 * ToDo convert all score to pinky location
 *
 * 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 "plugin.h"
#include "arcade.h"
#include "pacbox.h"
#include "pacbox_lcd.h"
#include "wsg3.h"
#include "lib/configfile.h"
#include "lib/playback_control.h"
#include "lib/helper.h"
static fb_data *lcd_fb;

/*Allows split screen jump and makes pacman invincible if you start at 18 credits (for testing purposes)*/
//#define CHEATS 1

/* Enable AI on all targets */
#define AI 1

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

static struct pacman_settings settings;
static struct pacman_settings old_settings;
static bool sound_playing = false;

#define SETTINGS_VERSION 2
#define SETTINGS_MIN_VERSION 2
#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* yesno_options[] = {"No", "Yes"};

#ifdef AI
static unsigned char ai_direction[15][205] =              /* level turn directions */
{
    {2,1,3,1,2,0,3,0,3,1,2,1,3,1,3,0,3,0,2,0,3,0,2,1,3,1,2,0,2,1,3,1,3,0,3,1,3,1,2,0,3,0,3,0,3,0,2,0,3,1,2,0,2,0,3,1,2,1,2,1,2,0,2,0,2,0,3,1,3,1,2,1,2,1,3,0,2,0,2,0,2,1,2,0,3,0,2,1,3,0,3,2,1,3,0,3,1,2,1,2,0,2,3,1,3,0,3,0,2,4}, /* first level */
    {2,0,3,1,3,1,2,0,2,0,2,0,3,1,3,1,2,1,2,0,3,0,3,0,3,1,3,1,2,1,2,0,2,0,3,0,2,0,3,1,2,0,3,1,2,0,2,1,2,1,2,0,2,0,3,1,0,3,0,3,2,0,2,0,2,0,3,1,0,3,2,0,2,1,2,1,3,0,3,1,3,1,3,0,3,0,1,2,1,3,1,3,1,2},/* second level*/
    {2,1,3,1,2,0,2,0,3,0,2,1,3,1,2,1,2,0,3,0,3,0,3,1,3,1,2,1,2,0,2,0,2,3,0,3,0,3,0,2,0,3,1,2,0,2,0,3,1,2,1,2,1,3,1,3,1,2,0,2,1,3,1,3,0,3,0,2,0,3,1,2,1,2,0,3,1,3,1,2,1,2,0,2,0,2,0,3,1,2,0,2,1,3,0,1,2,1,2,0},/*third level*/
    {2,1,3,1,2,0,2,0,3,0,2,1,3,1,2,1,2,0,3,0,3,0,3,1,3,1,2,1,2,0,2,0,2,3,0,3,0,3,0,2,0,3,1,2,0,2,0,3,1,2,1,2,1,3,1,3,1,2,0,2,0,3,1,3,1,2,1,2,0,2,0,3,0,2,0,3,0,2,1,2,1,3,0,3,0,2,1,0,3,1,3,1,2,1,2,1,3,1,3,0,3,0,2,0,2,1,2,1,3,1,3,0,3,1,3,2,0,2,0,2,0,2},/*level four*/
    {2,2,1,2,1,3,0,3,0,2,0,2,0,3,0,2,0,2,0,3,1,2,1,3,1,2,1,3,1,2,1,2,1,3,1,3,0,2,0,3,1,2,1,2,0,2,0,3,0,2,0,3,1,3,0,2,1,2,1,2,0,2,0,2,0,3,0,3,1,3,1,2,1,3,1,3,0,3,0,2,0,2,1,3,1,3,0,2,1,2,0,3,1,3,1,3,1,2,1,2,0,2,1,2,0,2,0,3,1,3,0,2,0,2},/*levels 5,7,8,11*/
    {2,2,1,2,1,3,0,3,0,2,0,2,0,3,0,2,0,2,0,3,1,2,1,3,1,2,1,3,1,2,1,2,1,3,1,3,0,2,0,3,1,2,1,2,0,2,0,3,0,2,0,3,1,3,0,2,1,2,1,2,0,3,0,2,0,3,1,3,0,2,1,2,1,2,0,2,0,3,0,2,1,2,0,3,0,2,1,3,0,2,1,3,1,3,0,3,1,2,1,2,1,2,0,2,0,3,1,3,0,1,0,3,1},/*level six*/
    {2,1,3,1,2,0,2,0,3,0,2,0,3,0,3,1,2,0,2,0,2,1,3,0,3,1,3,0,2,1,2,0,3,1,3,1,2,1,3,1,3,0,2,0,3,1,2,1,3,1,2,0,3,0,2,0,3,1,2,0,3,1,2,1,2,1,2,0,2,0,2,0,3,0,3,1,3,1,3,0,3,1,2,1,3,1,3,1,2,0,3,0,2,1,3,1,2,0,3,0,2,1,3,1,3,2,1,3,0,2,0,2,0,2,1,3,1,2,1,3,1,2,0,3,0,2,0,3,1,3,1,2,0,2,0,3,1,2,0,3,0,2,1,3,0,3,1,2,1,3,1,2,1,2,1,2,0,2,1,3,0,2,1,2,1,2,0,2,1,2,0,2,1,2,0,2,0,3,0,2,0,3,1,2,0,2,1,3,1,2,1,3,1,3,0,2},/*level nine*/
    {2,2,1,2,1,3,0,3,0,2,0,2,0,3,0,2,0,2,0,3,1,2,1,3,1,2,1,3,1,2,1,2,1,3,1,3,0,2,0,3,1,2,1,2,0,2,0,3,0,2,0,3,1,3,0,2,1,2,1,2,0,3,0,2,0,3,1,2,0,2,1,2,1,2,1,2,0,2,0,3,0,2,1,3,0,3,0,1,2,1,3,1,2,1,0,3,0,2,0,2,1,2,0,2,0,3,1,3,0,2,1,2,1,2,0,3,0,2,0,3,1,2,0,2,1,2,1,2,1,2,0,2,0,3,0,2,1,3,0,3,0,1,2,1,3,1,2,1,0,3,0,2,0,2,1,2,0,2,0,3,1,3,0,2,1,2,1,2,},/*level ten*/
    {2,1,3,1,2,0,2,0,3,0,2,0,3,0,3,1,2,0,2,0,2,1,3,0,3,1,3,0,2,1,2,0,3,1,3,1,2,1,3,1,3,0,2,0,3,1,2,1,3,1,2,0,3,0,2,0,3,1,2,0,3,1,2,1,2,1,2,0,2,0,2,0,3,0,3,1,3,1,3,0,3,1,2,1,3,1,3,1,2,0,3,0,2,1,3,1,2,0,3,0,2,1,3,1,3,2,1,3,0,2,0,2,0,2,1,3,1,2,1,3,1,2,0,3,0,2,0,3,1,3,1,2,0,2,0,3,1,2,0,3,0,2,1,3,0,1,3,0,3,1,3,0,2,1,3,1,3,1,2,1,2,1,3,0,2,1,2,1,2,0,2,0,2,0,3,1,2,1,2,1,2,0,2,0,2,0,3,1,3,1,2,0,2},/*level twelve*/
    {2,2,1,2,1,3,0,3,0,2,0,2,0,3,0,2,0,2,0,3,1,2,1,3,1,2,1,3,1,2,1,2,1,3,1,3,0,2,0,3,1,2,1,2,0,2,0,3,0,2,0,3,1,2,0,3,1,2,0,2,3,1,2,1,2,1,3,1,2,0,2,0,2,0,3,1,2,1,2,1,2,0,2,1,2,1,3,1,3,0,3,0,3,0,3,0,2,1,3,1,3,1,3,0,3,0,2,0,3,0,2,1,3,0,3,1,3,2,1},/*level fourteen*/
    {2,1,3,1,2,0,2,0,3,0,2,0,3,0,3,1,2,0,2,0,2,1,3,0,3,1,3,0,2,1,2,0,3,1,3,1,2,1,3,1,3,0,2,0,3,1,2,1,3,1,2,0,3,0,2,0,3,1,2,0,3,1,2,1,2,1,2,0,2,0,2,0,3,0,3,1,3,1,3,0,3,1,2,1,3,1,3,1,2,0,3,0,2,1,3,1,2,0,3,0,2,1,3,1,3,2,1,3,0,2,0,2,0,2,1,3,1,2,1,3,1,2,0,3,0,2,0,3,1,3,1,2,0,3,0,3,1,2,1,3,1,3,0,2,0,3,0,2,0,3,0,2,1,3,1,3,0,3,0,3,0,2,1,3,1,2,1,3,0,3,1,2,0,3,0,2,0,2,0,3,1,0,1,2,1,3,0,2,0,2,1,2,0,2,1,3,2},
    {2,2,1,2,1,3,0,3,0,2,0,3,0,2,0,2,0,3,0,2,1,3,1,2,1,3,1,3,1,3,1,2,0,2,0,2,0,3,1,2,0,2,0,2,0,3,0,3,1,3,0,3,1,2,1,2,0,2,0,3,0,2,1,3,1,2,0,2,0,2,1,3,1,2,1,3,1,2,1,3,0,2,0,3,0,2},
    {2,2,1,2,1,3,0,3,0,2,0,3,0,2,0,2,0,3,0,2,1,3,1,2,1,3,1,3,1,3,1,2,0,2,0,2,0,3,1,2,0,2,0,2,0,3,0,3,1,3,1,2,1,3,1,2,1,2,1,3,1,3,0,2,0,3,1,3,0,2,1,3,1,2,0,2,1,3,1,2,1,3,1,3,1,3,1,2,1},
    {2,1,3,1,2,0,3,0,3,1,2,1,3,1,3,0,3,0,3,0,2,1,3,1,3,0,3,0,2,0,2,1,2,1,3,1,2,1,2,0,2,0,3,0,2,0,3,0,3,1,2,0,2,1,3,1,3,1,3,0,3,0,2,1,3,1,2,1,2,0,2,0,3,0,3,1,2,1,3,0},
    {2,1,3,1,2,0,3,0,2,0,3,1,2,0,3,1,2,1,2,0,2,0,2,0,3,0,3,0,2,1,3,0,2,1,3,1,2,1,2,1,2,0,2,1,2,1,2,1,3,0,1,3,0,1,2,1,2,1,2,0,3,1,2}
};
static unsigned char ai_location[15][205] =               /* level turn locations */
{
    {0,52,58,57,61,34,60,37,54,43,55,42,58,43,61,46,60,49,57,48,54,49,51,42,52,43,55,39,54,34,55,34,58,37,26,52,58,57,61,48,60,49,57,52,42,57,39,48,35,57,40,54,39,48,35,52,37,51,40,48,43,45,42,42,39,39,35,43,37,49,40,48,43,42,49,49,45,45,42,42,39,39,40,34,39,43,35,34,40,37,39,3,39,55,52,54,57,55,57,58,54,57,3,52,58,55,57,57,54,53},   /* first level */
    {0,47,54,52,58,57,61,45,60,42,57,39,54,43,55,49,58,48,61,34,60,37,54,40,51,49,52,57,55,57,58,54,54,51,48,52,39,48,35,57,40,54,39,57,40,54,35,48,37,40,56,37,57,33,54,37,2,54,40,51,4,42,48,39,39,34,35,37,1,39,4,45,35,39,37,34,40,37,39,40,40,43,43,46,42,49,16,40,48,42,48,45,52,55}, /*second level */
    {0,52,58,57,61,45,60,42,57,43,54,39,55,49,58,48,61,34,60,37,54,40,51,49,52,57,55,57,58,54,54,51,51,4,49,48,52,42,57,39,48,35,57,40,54,39,48,35,52,37,51,40,48,43,49,46,52,55,39,54,34,55,34,58,37,57,43,54,42,51,49,52,48,55,39,35,43,37,49,40,48,43,45,42,42,39,39,35,43,37,39,35,34,37,43,3,37,39,40,34}, /*third level */
    {0,52,58,57,61,45,60,42,57,43,54,39,55,49,58,48,61,34,60,37,54,40,51,49,52,57,55,57,58,54,54,51,51,4,49,48,52,42,57,39,48,35,57,40,54,39,48,35,52,37,51,40,48,43,49,46,52,55,45,54,39,48,40,49,49,52,48,55,45,54,42,45,43,42,42,39,43,35,39,37,34,40,37,39,43,35,34,1,35,37,37,49,40,48,43,42,52,43,55,46,54,49,51,42,48,39,52,34,55,34,58,37,54,43,55,4,45,54,42,48,39,1}, /*fourth level */
    {0,14,39,58,34,61,46,60,49,57,45,54,42,45,43,42,42,39,39,35,43,37,42,40,43,43,42,49,49,52,48,55,42,58,43,61,57,60,54,54,57,55,57,58,54,57,48,54,52,39,48,35,52,37,57,35,54,46,51,49,42,48,39,42,34,39,37,35,43,37,49,40,48,43,49,46,52,42,57,39,39,35,34,37,40,3,43,35,39,40,34,39,40,40,43,43,49,52,48,55,45,54,39,58,36,57,34,54,37,55,43,54,39,51}, /*fifth level */
    {0,14,39,58,34,61,46,60,49,57,45,54,42,45,43,42,42,39,39,35,43,37,42,40,43,43,42,49,49,52,48,55,42,58,43,61,57,60,54,54,57,55,57,58,54,57,48,54,52,39,48,35,52,37,57,35,54,46,51,49,42,45,43,42,42,39,52,40,57,39,51,40,48,43,45,42,42,39,43,35,39,40,34,39,37,35,34,37,37,35,34,3,37,46,40,45,49,49,42,52,39,58,36,57,34,54,37,55,52,4,54,48,37,},/*sixth level*/
    {0,52,58,57,61,45,60,42,57,43,54,42,51,49,48,52,55,45,54,42,48,39,52,40,45,49,52,52,48,51,49,42,45,49,52,52,55,42,58,43,61,57,60,54,54,57,55,57,58,57,61,48,60,49,57,48,54,52,55,48,54,52,55,51,58,48,61,45,60,42,57,39,48,40,45,49,46,40,49,49,48,52,52,48,55,52,58,57,61,48,60,49,57,42,58,43,61,34,60,37,54,34,55,34,58,1,34,61,43,60,42,57,39,48,54,52,57,55,57,58,57,61,48,60,49,57,39,48,40,52,43,55,39,42,34,39,37,40,34,39,37,35,34,37,37,35,43,37,42,40,43,43,42,46,51,49,42,48,39,55,52,48,51,49,42,52,39,48,54,52,51,51,42,52,39,48,54,42,57,39,54,35,57,37,4,35,48,37,49,40,48,43,49,46,52,39},/*ninth level*/
    {0,14,39,58,34,61,46,60,49,57,45,54,42,45,43,42,42,39,39,35,43,37,42,40,43,43,42,49,49,52,48,55,42,58,43,61,57,60,54,54,57,55,57,58,54,57,48,54,52,39,48,35,52,37,57,35,54,46,51,49,42,45,43,42,42,39,57,40,54,39,51,40,48,43,42,46,39,42,34,39,37,35,34,40,37,39,43,3,37,42,40,43,43,42,4,45,43,42,42,39,39,58,36,57,34,54,37,55,52,48,51,49,42,52,45,60,},/*tenth level*/
    {0,52,58,57,61,45,60,42,57,43,54,42,51,49,48,52,55,45,54,42,48,39,52,40,45,49,52,52,48,51,49,42,45,49,52,52,55,42,58,43,61,57,60,54,54,57,55,57,58,57,61,48,60,49,57,48,54,52,55,48,54,52,55,51,58,48,61,45,60,42,57,39,48,40,45,49,46,40,49,49,48,52,52,48,55,52,58,57,61,48,60,49,57,42,58,43,61,34,60,37,54,34,55,34,58,1,34,61,43,60,42,57,39,48,54,52,57,55,57,58,57,61,48,60,49,57,39,48,40,52,43,55,39,42,34,39,37,40,34,39,37,35,34,37,37,4,46,40,45,49,46,52,39,42,40,43,43,49,49,42,52,39,55,52,48,51,49,42,52,39,48,54,39,48,35,57,37,51,40,48,43,45,42,42,39,39,35,43,37,57,40,54,35},/*twelfth level*/
    {0,14,39,58,34,61,46,60,49,57,45,54,42,45,43,42,42,39,39,35,43,37,42,40,43,43,42,49,49,52,48,55,42,58,43,61,57,60,54,54,57,55,57,58,54,57,48,54,52,39,48,35,52,37,48,35,57,37,54,35,4,52,37,51,40,48,43,49,49,42,48,39,42,34,39,57,40,54,46,51,49,42,48,39,52,34,55,34,58,37,54,40,51,49,48,52,39,42,40,43,43,49,52,52,48,37,42,34,39,37,35,34,37,37,35,43,37,4,39},/*fourteenth level*/
    {0,52,58,57,61,45,60,42,57,43,54,42,51,49,48,52,55,45,54,42,48,39,52,40,45,45,52,52,48,51,49,42,45,49,52,52,55,42,58,43,61,57,60,54,54,57,55,57,58,57,61,48,60,49,57,48,54,52,55,48,54,52,55,51,58,48,61,45,60,42,57,39,48,40,45,49,46,40,49,49,48,52,52,48,55,52,58,57,61,48,60,49,57,42,58,43,61,34,60,37,54,34,55,34,58,1,34,61,43,60,42,57,39,48,54,52,57,55,57,58,57,61,48,60,49,57,39,48,40,52,43,55,39,54,40,45,49,49,42,52,43,55,52,54,51,48,37,41,34,39,37,35,34,37,37,46,40,45,46,42,49,39,42,40,43,43,42,49,49,48,52,55,39,54,40,48,54,39,48,35,57,2,3,37,54,40,57,39,54,35,48,37,45,35,39,37,40},
    {0,14,39,58,34,61,46,60,49,57,48,54,52,48,51,45,48,42,49,39,42,40,43,43,42,49,49,52,52,58,57,61,45,60,42,57,39,39,52,55,45,54,39,42,34,39,37,35,43,37,46,35,52,46,51,49,42,48,54,42,57,35,54,37,57,40,54,39,45,35,34,40,37,52,34,55,34,58,34,61,57,60,57,57,57,53},
    {0,14,39,58,34,61,46,60,49,57,48,54,52,48,51,45,48,42,49,39,42,40,43,43,42,49,49,52,52,58,57,61,45,60,42,57,39,39,52,55,45,54,39,42,34,39,37,35,43,37,49,40,48,43,49,49,42,52,34,55,34,58,37,42,34,35,43,37,57,35,54,37,57,40,54,35,48,37,49,40,48,43,49,46,52,52,57,55,57},
    {0,52,58,57,61,34,60,37,54,43,55,42,58,43,61,46,60,49,57,52,54,48,55,52,58,55,57,57,54,54,48,51,49,42,52,43,55,39,58,36,57,34,54,37,42,34,39,37,35,43,37,39,35,34,37,37,46,40,49,49,48,52,35,48,37,49,40,48,43,45,42,42,39,52,35,57,37,54,40,57},
    {0,52,58,57,61,48,60,49,57,48,54,52,55,48,54,57,55,57,58,54,48,51,45,48,42,49,39,52,35,48,37,52,34,48,37,52,46,51,49,45,37,39,33,38,35,35,49,34,60,37,53,60,42,55,60,38,35,58,40,54,35,57,37}
};
#endif

static struct configdata config[] =
{
   {TYPE_ENUM, 0, 2, { .int_p = &settings.difficulty }, "Difficulty",
    difficulty_options},
   {TYPE_ENUM, 0, 4, { .int_p = &settings.numlives }, "Pacmen Per Game",
    numlives_options},
   {TYPE_ENUM, 0, 4, { .int_p = &settings.bonus }, "Bonus", bonus_options},
   {TYPE_ENUM, 0, 2, { .int_p = &settings.ghostnames }, "Ghost Names",
    ghostnames_options},
   {TYPE_ENUM, 0, 2, { .int_p = &settings.showfps }, "Show FPS",
    yesno_options},
   {TYPE_ENUM, 0, 2, { .int_p = &settings.sound }, "Sound",
    yesno_options},
#ifdef AI
   {TYPE_ENUM, 0, 2, { .int_p = &settings.ai }, "AI",
    yesno_options},
#endif
};

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

    rb->snprintf(filename,sizeof(filename), ROCKBOX_DIR "/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 selected=0;
    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 },
    };

    enum
    {
        PBMI_DIFFICULTY = 0,
        PBMI_PACMEN_PER_GAME,
        PBMI_BONUS_LIFE,
        PBMI_GHOST_NAMES,
        PBMI_DISPLAY_FPS,
        PBMI_SOUND,
#ifdef AI
        PBMI_AI,
#endif
        PBMI_RESTART,
        PBMI_QUIT,
    };

    MENUITEM_STRINGLIST(menu, "Pacbox", NULL,
                        "Difficulty", "Pacmen Per Game", "Bonus Life",
                        "Ghost Names", "Display FPS", "Sound",
#ifdef AI
                        "AI",
#endif
                        "Restart", "Quit");

    rb->button_clear_queue();

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256)
    rb->lcd_set_mode(LCD_MODE_RGB565);
#endif

    while (!menu_quit) {
        result=rb->do_menu(&menu, &selected, NULL, false);

        switch(result)
        {
            case PBMI_DIFFICULTY:
                new_setting=settings.difficulty;
                rb->set_option("Difficulty", &new_setting, RB_INT,
                               difficulty_options , 2, NULL);
                if (new_setting != settings.difficulty) {
                    settings.difficulty=new_setting;
                    need_restart=true;
                }
                break;
            case PBMI_PACMEN_PER_GAME:
                new_setting=settings.numlives;
                rb->set_option("Pacmen Per Game", &new_setting, RB_INT,
                               numlives_options , 4, NULL);
                if (new_setting != settings.numlives) {
                    settings.numlives=new_setting;
                    need_restart=true;
                }
                break;
            case PBMI_BONUS_LIFE:
                new_setting=settings.bonus;
                rb->set_option("Bonus Life", &new_setting, RB_INT,
                               bonus_options , 4, NULL);
                if (new_setting != settings.bonus) {
                    settings.bonus=new_setting;
                    need_restart=true;
                }
                break;
            case PBMI_GHOST_NAMES:
                new_setting=settings.ghostnames;
                rb->set_option("Ghost Names", &new_setting, RB_INT,
                               ghostname_options , 2, NULL);
                if (new_setting != settings.ghostnames) {
                    settings.ghostnames=new_setting;
                    need_restart=true;
                }
                break;
            case PBMI_DISPLAY_FPS:
                rb->set_option("Display FPS",&settings.showfps, RB_INT,
                               noyes, 2, NULL);
                break;
            case PBMI_SOUND:
                rb->set_option("Sound",&settings.sound, RB_INT,
                               noyes, 2, NULL);
                break;
#ifdef AI
            case PBMI_AI:
                rb->set_option("AI",&settings.ai, RB_INT,
                               noyes, 2, NULL);
                break;
#endif
            case PBMI_RESTART:
                need_restart=true;
                menu_quit=1;
                break;
            default:
                menu_quit=1;
                break;
        }
    }

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256)
    rb->lcd_set_mode(LCD_MODE_PAL256);
#endif

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

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

/* Sound is emulated in ISR context, so not much is done per sound frame */
#define NBSAMPLES    128
static uint32_t sound_buf[NBSAMPLES];
#if CONFIG_CPU == MCF5249
/* Not enough to put this in IRAM */
static int16_t raw_buf[NBSAMPLES];
#else
static int16_t raw_buf[NBSAMPLES] IBSS_ATTR;
#endif

/*
    Audio callback
 */
static void get_more(const void **start, size_t *size)
{
    int32_t *out, *outend;
    int16_t *raw;

    /* Emulate the audio for the current register settings */
    playSound(raw_buf, NBSAMPLES);

    out = sound_buf;
    outend = out + NBSAMPLES;
    raw = raw_buf;

    /* Convert to stereo */
    do
    {
        uint32_t sample = (uint16_t)*raw++;
        *out++ = sample | (sample << 16);
    }
    while (out < outend);

    *start = sound_buf;
    *size = NBSAMPLES*sizeof(sound_buf[0]);
}

/*
    Start the sound emulation
*/
static void start_sound(void)
{
    int sr_index;

    if (sound_playing)
        return;

    rb->audio_stop();

#ifndef USE_IRAM
    /* Ensure control of PCM - stopping music itn't obligatory */
    rb->plugin_get_audio_buffer(NULL);
#endif

    /* Get the closest rate >= to what is preferred */
    const struct pcm_sink_caps* caps = rb->pcm_current_sink_caps();
    sr_index = rb->round_value_to_list32(PREFERRED_SAMPLING_RATE,
                        caps->samprs, caps->num_samprs, false);

    if (caps->samprs[sr_index] < PREFERRED_SAMPLING_RATE && sr_index > 0)
    {
        /* Round up */
        sr_index--;
    }

    wsg3_set_sampling_rate(caps->samprs[sr_index]);

    static const struct mixer_play_cbs cbs = {
        .get_more = get_more,
    };
    rb->mixer_set_frequency(caps->samprs[sr_index]);
    rb->pcmbuf_fade(false, true); /* Be sure channel is audible */
    rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, &cbs, NULL, 0);

    sound_playing = true;
}

/*
    Stop the sound emulation
*/
static void stop_sound(void)
{
    if (!sound_playing)
        return;

    rb->pcmbuf_fade(false, false); /* Mute channel */
    rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
    rb->mixer_set_frequency(HW_SAMPR_DEFAULT);

    sound_playing = false;
}

/* use buttons for joystick */
void joystick(void)
{
    int status;
    /* Check the button status */
    status = rb->button_status();
    rb->button_clear_queue();
    /*handle buttons if AI is off */
#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
#ifdef CHEATS
// skip level for testing purposes
    if(status == SKIP_LEVEL)
    {
//dots
        ram_[0x4E0E] = 242;
//level
        ram_[0x4E13] = 254;
    }
#endif
}
#ifdef AI
/* blank controls */
void clear_joystick(void)
{
    setDeviceMode( Joy1_Left, DeviceOff);
    setDeviceMode( Joy1_Right, DeviceOff);
    setDeviceMode( Joy1_Up, DeviceOff);
    setDeviceMode( Joy1_Down, DeviceOff);

}

/* Make turns */
void ai_turn( unsigned char level, unsigned char turn)
{
     switch(ai_direction[level][turn])
            {
                case 0:
                    clear_joystick();
                    setDeviceMode( Joy1_Up, DeviceOn);
                    break;
                case 1:
                    clear_joystick();
                    setDeviceMode( Joy1_Down, DeviceOn);
                    break;
                case 2:
                    clear_joystick();
                    setDeviceMode( Joy1_Right, DeviceOn);
                    break;
                case 3:
                    clear_joystick();
                    setDeviceMode( Joy1_Left, DeviceOn);
                    break;
                case 4:
                    clear_joystick();
                    break;
            }
}
/*
    Decide turns automatically
*/
unsigned char ai( unsigned char turn )
{
    unsigned char position;                       /* pac-mans current position */
    unsigned char level;   /* current game level */
    unsigned char map[20] = {0,1,2,3,4,5,4,4,6,7,4,8,8,9,10,10,11,10,12,12};

    /*Select level map*/
    if(ram_[0x4E13] < 20)
        level=map[ram_[0x4E13]];
    else if(ram_[0x4E13] != 255)
        level=13;
    else
        level=14;

    /* AI can't start in middle of a level */
    if( turn > 210)
    {
        rb->splash(HZ/2, "AI will engage at next level start");
        return 0;
    }

    /* reset joystick direction on level start */
    if(!(ram_[0x4E0E] || turn))
    {
        /*levels that start facing right */
        if((level != 4) && (level != 11) && (level != 7) && (level != 5) && (level != 9) && (level !=12))
            clear_joystick();
        else
        {
            clear_joystick();
            setDeviceMode( Joy1_Right, DeviceOn);
        }
        return 1;
    }
    if( turn == 0)
    {
        if( (ram_[0x4E0E] == 1) && (ram_[0x4D3A] == 47))
        {
            turn=1;
        }
        joystick();
        return turn;
    }

    if((turn != 0) && (turn !=209))
    {
        /* set which axis to look for pac-man along */
        position = ram_[0x4D3A];
        if( ai_direction[level][turn-1] < 2)
        {
            position = ram_[0x4D39];
        }


        /*move joystick if necessary */
            if(ai_location[level][turn] < 30)
            {
                if((ai_location[level][turn] < 10) && (ai_location[level][turn] > 0)) /* handle turns using ghosts eaten as basis for turn timing */
                {
                    if( ram_[0x4DD0] == ai_location[level][turn])
                    {
                        ai_turn(level,turn);
                        turn++;
                    }
                }

                if( ram_[0x4D31] == (ai_location[level][turn] + 30)) /* handle turns using pinky's location as basis for turn timing */
                {
                    ai_turn(level,turn);
                    turn++;
                }
            }else if( position == ai_location[level][turn] ) /* handle turns using pacman's location as basis for turn timing */
            {
                ai_turn(level,turn);
                turn++;
            }
    }

    /* reset turn counter and joystick direction on level start */
    if(ram_[0x4E0E] == 0 )
    {
        /*levels that start facing right */
        if((level != 4) && (level != 11) && (level != 7) && (level != 5) && (level != 9) && (level !=12))
            clear_joystick();
        else
        {
            clear_joystick();
            setDeviceMode( Joy1_Right, DeviceOn);
        }
        return 1;
    }
    return turn;
}
#endif

/*
    Runs the game engine for one frame.
*/
static int gameProc( void )
{
    int fps;
    int status;
    long end_time;
    int frame_counter = 0;
    int yield_counter = 0;
#ifdef AI
    unsigned char turn = 250;
#endif

    if (settings.sound)
        start_sound();

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

/*Make Pac-man invincible*/
#ifdef CHEATS
        if(ram_[0x4E6E]== 23)
            ram_[0x4DA5]=00;
#endif



        frame_counter++;

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

#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
        ) {
            bool menu_res;

            end_time = *rb->current_tick;

            stop_sound();

            menu_res = pacbox_menu();

            rb->lcd_clear_display();
#ifdef HAVE_REMOTE_LCD
            rb->lcd_remote_clear_display();
            rb->lcd_remote_update();
#endif
            if (menu_res)
                return 1;

            if (settings.sound)
                start_sound();

            start_time += *rb->current_tick-end_time;
        }
#ifdef AI
        if(!settings.ai)
        {
            joystick();
            turn = 250;
        }
        /* run ai */
        if (settings.ai && !ram_[0x4E02])
            turn = ai(turn);
        else
            joystick();
#else
        joystick();
#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 );

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256)
            rb->lcd_blit_pal256(    video_buffer, 0, 0, XOFS, YOFS,
                                    ScreenWidth, ScreenHeight);
#else
            blit_display(lcd_fb ,video_buffer);
#endif

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

#if !defined(HAVE_LCD_MODES) || \
    defined(HAVE_LCD_MODES) && !(HAVE_LCD_MODES & LCD_MODE_PAL256)
            rb->lcd_update();
#endif

            /* 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);
            }
        }
    }

    stop_sound();

    return 0;
}

enum plugin_status plugin_start(const void* parameter)
{
    (void)parameter;

#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();

    struct viewport *vp_main = rb->lcd_set_viewport(NULL);
    lcd_fb = vp_main->buffer->fb_ptr;

    /* 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 */
    settings.sound = 0;      /* Sound off by default */
    settings.ai = 0;         /* AI off by default */

    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;

#ifdef HAVE_BACKLIGHT
    /*Turn off backlight for ai*/
    if(settings.ai)
        backlight_ignore_timeout();
#endif

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

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256)
    rb->lcd_set_mode(LCD_MODE_PAL256);
#endif

    /* 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, "Saving settings...");
            configfile_save(SETTINGS_FILENAME, config,
                            sizeof(config)/sizeof(*config),
                            SETTINGS_VERSION);
        }
    } else {
        rb->splashf(HZ*2, "No ROMs in %s/pacman/", ROCKBOX_DIR);
    }

#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_PAL256)
    rb->lcd_set_mode(LCD_MODE_RGB565);
#endif

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

    backlight_use_settings();

    return PLUGIN_OK;
}
