#include "plugin.h"
#include "lib/display_text.h"
#include "lib/helper.h"
#include "lib/playback_control.h"
#include "lib/pluginlib_exit.h"
#include "lib/pluginlib_actions.h"

#define BUTTON_ROWS 6
#define BUTTON_COLS 5

#define REC_HEIGHT (int)(LCD_HEIGHT / (BUTTON_ROWS + 1))
#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS) 

#define Y_7_POS (LCD_HEIGHT)           /* Leave room for the border */
#define Y_6_POS (Y_7_POS - REC_HEIGHT) /* y6 = 63 */
#define Y_5_POS (Y_6_POS - REC_HEIGHT) /* y5 = 53 */
#define Y_4_POS (Y_5_POS - REC_HEIGHT) /* y4 = 43 */
#define Y_3_POS (Y_4_POS - REC_HEIGHT) /* y3 = 33 */
#define Y_2_POS (Y_3_POS - REC_HEIGHT) /* y2 = 23 */
#define Y_1_POS (Y_2_POS - REC_HEIGHT) /* y1 = 13 */
#define Y_0_POS 0                      /* y0 = 0  */

#define X_0_POS 0                     /* x0 = 0  */
#define X_1_POS (X_0_POS + REC_WIDTH) /* x1 = 22 */
#define X_2_POS (X_1_POS + REC_WIDTH) /* x2 = 44 */
#define X_3_POS (X_2_POS + REC_WIDTH) /* x3 = 66 */
#define X_4_POS (X_3_POS + REC_WIDTH) /* x4 = 88 */
#define X_5_POS (X_4_POS + REC_WIDTH) /* x5 = 110, column 111 left blank */

#if (CONFIG_KEYPAD == IPOD_1G2G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || (CONFIG_KEYPAD == IPOD_4G_PAD)
#define DARTS_QUIT PLA_SELECT_REPEAT
#else
#define DARTS_QUIT PLA_CANCEL
#endif
#define DARTS_SELECT PLA_SELECT
#define DARTS_RIGHT PLA_RIGHT
#define DARTS_LEFT PLA_LEFT
#define DARTS_UP PLA_UP
#define DARTS_DOWN PLA_DOWN
#define DARTS_RRIGHT PLA_RIGHT_REPEAT
#define DARTS_RLEFT PLA_LEFT_REPEAT
#define DARTS_RUP PLA_UP_REPEAT
#define DARTS_RDOWN PLA_DOWN_REPEAT

#define RESUME_FILE PLUGIN_GAMES_DATA_DIR "/dart_scorer.save"
/* leave first line blank on bitmap display, for pause icon */
#define FIRST_LINE 1

#define NUM_PLAYERS 2
#define MAX_UNDO 100
#define CENTRE_X (LCD_WIDTH/2)

static const struct button_mapping *plugin_contexts[] = {pla_main_ctx};

/* game data structures */
enum game_mode
{
    five,
    three
};
static struct settings_struct
{
    enum game_mode mode;
    int scores[2];
    bool turn;
    int throws;
    int history[MAX_UNDO];
    int history_ptr;
} settings;

/* temporary data */
static bool loaded = false;     /* has a save been loaded? */
int btn_row, btn_col;           /* current position index for button */
int prev_btn_row, prev_btn_col; /* previous cursor position      */
unsigned char *buttonChar[6][5] = {
    {"", "x1", "x2", "x3", ""},
    {"1", "2", "3", "4", "5"},
    {"6", "7", "8", "9", "10"},
    {"11", "12", "13", "14", "15"},
    {"16", "17", "18", "19", "20"},
    {"", "Miss", "Bull", "Undo", ""}};
int modifier;

static int do_dart_scorer_pause_menu(void);
static void drawButtons(void);

/* First, increases *dimen1 by dimen1_delta modulo dimen1_modulo.
   If dimen1 wraps, increases *dimen2 by dimen2_delta modulo dimen2_modulo.
*/
static void move_with_wrap_and_shift(
    int *dimen1, int dimen1_delta, int dimen1_modulo,
    int *dimen2, int dimen2_delta, int dimen2_modulo)
{
    bool wrapped = false;

    *dimen1 += dimen1_delta;
    if (*dimen1 < 0)
    {
        *dimen1 = dimen1_modulo - 1;
        wrapped = true;
    }
    else if (*dimen1 >= dimen1_modulo)
    {
        *dimen1 = 0;
        wrapped = true;
    }

    if (wrapped)
    {
        /* Make the dividend always positive to be sure about the result.
           Adding dimen2_modulo does not change it since we do it modulo. */
        *dimen2 = (*dimen2 + dimen2_modulo + dimen2_delta) % dimen2_modulo;
    }
}

static void drawButtons()
{
    int i, j, w, h;
    for (i = 0; i <= 5; i++)
    {
        for (j = 0; j <= 4; j++)
        {
            unsigned char button_text[16];
            char *selected_prefix = (i == 0 && modifier > 0 && j == modifier) ? "*" : "";
            rb->snprintf(button_text, sizeof(button_text), "%s%s", selected_prefix, buttonChar[i][j]);
            rb->lcd_getstringsize(button_text, &w, &h);
            if (i == btn_row && j == btn_col) /* selected item */
                rb->lcd_set_drawmode(DRMODE_SOLID);
            else
                rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
            rb->lcd_fillrect(X_0_POS + j * REC_WIDTH,
                             Y_1_POS + i * REC_HEIGHT,
                             REC_WIDTH, REC_HEIGHT + 1);
            if (i == btn_row && j == btn_col) /* selected item */
                rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
            else
                rb->lcd_set_drawmode(DRMODE_SOLID);
            rb->lcd_putsxy(X_0_POS + j * REC_WIDTH + (REC_WIDTH - w) / 2,
                           Y_1_POS + i * REC_HEIGHT + (REC_HEIGHT - h) / 2 + 1,
                           button_text);
        }
    }
    rb->lcd_set_drawmode(DRMODE_SOLID);
}

static void draw(void)
{
    rb->lcd_clear_display();

    unsigned int w,h;
    char buf[16];

    char *turn_marker;

    /* player 1 score */
    turn_marker = (0 == settings.turn) ? "*" : "";
#if LCD_HEIGHT <= 64 || LCD_WIDTH <= 96
    rb->snprintf(buf, sizeof(buf), "%sP1 %d", turn_marker, settings.scores[0]);
#else
    rb->snprintf(buf, sizeof(buf), "%sPlayer 1: %d", turn_marker, settings.scores[0]);
#endif
    rb->lcd_putsxy(0, 10, buf);

    /* player 2 score */
    turn_marker = (1 == settings.turn) ? "*" : "";
#if LCD_HEIGHT <= 64 || LCD_WIDTH <= 96
    rb->snprintf(buf, sizeof(buf), "%sP2 %d", turn_marker, settings.scores[1]);
#else
    rb->snprintf(buf, sizeof(buf), "%sPlayer 2: %d", turn_marker, settings.scores[1]);
#endif
    rb->lcd_getstringsize(buf, &w, &h);
    rb->lcd_putsxy(LCD_WIDTH - w, 10, buf);

    int throws_x = CENTRE_X - 5;
    for (int i = 0; i < settings.throws; ++i, throws_x += 5)
    {
        rb->lcd_putsxy(throws_x, 10, "|");
    }

    drawButtons();

    rb->lcd_update();
}

/*****************************************************************************
* save_game() saves the current game state.
******************************************************************************/
static void save_game(void)
{
    int fd = rb->open(RESUME_FILE, O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
        return;

    rb->write(fd, &settings, sizeof(struct settings_struct));

    rb->close(fd);
    rb->lcd_update();
}

/* load_game() loads the saved game and returns load success.*/
static bool load_game(void)
{
    signed int fd;
    bool loaded = false;

    /* open game file */
    fd = rb->open(RESUME_FILE, O_RDONLY);
    if (fd < 0)
        return false;

    /* read in saved game */
    if (rb->read(fd, &settings, sizeof(struct settings_struct)) == (long)sizeof(struct settings_struct))
    {
        loaded = true;
    }

    rb->close(fd);

    return loaded;
    return false;
}

/* asks the user if they wish to quit */
static bool confirm_quit(void)
{
    const struct text_message prompt = {(const char *[]){"Are you sure?", "This will clear your current game."}, 2};
    enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
    if (response == YESNO_NO)
        return false;
    else
        return true;
}

/* displays the help text */
static bool do_help(void)
{

#ifdef HAVE_LCD_COLOR
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_set_background(LCD_BLACK);
#endif

    rb->lcd_setfont(FONT_UI);

    static char *help_text[] = {"Dart Scorer", "", "", "Keep score of your darts game."};

    struct style_text style[] = {
        {0, TEXT_CENTER | TEXT_UNDERLINE},
    };

    return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
}

static void undo(void)
{
    if (!settings.history_ptr)
    {
        rb->splash(HZ * 2, "Out of undos!");
        return;
    }

    /* jumping back to previous player? */
    int turn = settings.throws == 3 ? !settings.turn : settings.turn;
    if (turn != settings.turn)
    {
        settings.throws = 0;
        settings.turn ^= true;
    }

    if (settings.history[settings.history_ptr - 1] >= 0)
    {
        settings.scores[turn] += settings.history[--settings.history_ptr];
        ++settings.throws;
    }
    else
    {
        /*
        negative history means we bust. negative filled for all skipped throws
        from being bust so consume back until no more
        */
        for (; settings.throws < 3 && settings.history[settings.history_ptr - 1] < 0; --settings.history_ptr, ++settings.throws)
        {
        }
    }
}

static void init_game(bool newgame)
{
    if (newgame)
    {
        /* initialize the game context */
        modifier = 1;
        btn_row = 1;
        btn_col = 0;

        int game_mode = -1;
        MENUITEM_STRINGLIST(menu, "Game Mode", NULL, "501", "301");
        while (game_mode < 0)
        {
            switch (rb->do_menu(&menu, &game_mode, NULL, false))
            {
            case 0:
            {
                settings.mode = five;
                break;
            }
            case 1:
            {
                settings.mode = three;
                break;
            }
            }

            for (int i = 0; i < NUM_PLAYERS; ++i)
            {
                settings.scores[i] = (settings.mode == five) ? 501 : 301;
            }
            settings.turn = false;
            settings.throws = 3;
            settings.history_ptr = 0;
            rb->lcd_clear_display();
        }
    }
}

/* main game loop */
static enum plugin_status do_game(bool newgame)
{
    init_game(newgame);
    draw();

    while (1)
    {
        /* wait for button press */
        int button = pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts));
        unsigned char *selected = buttonChar[btn_row][btn_col];
        switch (button)
        {
        case DARTS_SELECT:
            if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "x1")))
                modifier = 1;
            else if (!rb->strcmp(selected, "x2"))
                modifier = 2;
            else if (!rb->strcmp(selected, "x3"))
                modifier = 3;
            else if (!rb->strcmp(selected, "Undo"))
            {
                undo();
                continue;
            }
            else
            {
                /* main logic of score keeping */
                if (modifier == 0)
                    modifier = 1;
                int hit = (!rb->strcmp(selected, "Bull")) ? 25 : rb->atoi(selected);
                if (hit == 25 && modifier == 3)
                {
                    /* no triple bullseye! */
                    rb->splash(HZ * 2, "Triple Bull... Don't be silly!");
                    continue;
                }
                hit *= modifier;
                if (hit > settings.scores[settings.turn])
                {
                    rb->splash(HZ * 2, "Bust! End of turn.");
                    for (int i = 0; i < settings.throws; ++i)
                        settings.history[settings.history_ptr++] = -1;
                    settings.throws = 0;
                }
                else if (hit == settings.scores[settings.turn] - 1)
                {
                    rb->splash(HZ * 2, "1 left! Must checkout with a double. End of turn.");
                    for (int i = 0; i < settings.throws; ++i)
                        settings.history[settings.history_ptr++] = -1;
                    settings.throws = 0;
                }
                else if (hit == settings.scores[settings.turn] && modifier != 2)
                {
                    rb->splash(HZ * 2, "Must checkout with a double! End of turn.");
                    for (int i = 0; i < settings.throws; ++i)
                        settings.history[settings.history_ptr++] = -1;
                    settings.throws = 0;
                }
                else
                {
                    settings.scores[settings.turn] -= hit;
                    --settings.throws;
                    settings.history[settings.history_ptr++] = hit;
                    modifier = 1;
                    if (!settings.scores[settings.turn])
                        goto GAMEOVER;
                }

                if (!settings.throws)
                {
                    settings.throws = 3;
                    settings.turn ^= true;
                }
            }
            break;
        case DARTS_LEFT:
        case DARTS_RLEFT:
            move_with_wrap_and_shift(
                &btn_col, -1, BUTTON_COLS,
                &btn_row, 0, BUTTON_ROWS);
            break;
        case DARTS_RIGHT:
        case DARTS_RRIGHT:
            move_with_wrap_and_shift(
                &btn_col, 1, BUTTON_COLS,
                &btn_row, 0, BUTTON_ROWS);
            break;
#ifdef DARTS_UP
        case DARTS_UP:
        case DARTS_RUP:
#ifdef HAVE_SCROLLWHEEL
        case PLA_SCROLL_BACK:
        case PLA_SCROLL_BACK_REPEAT:
#endif
            move_with_wrap_and_shift(
                &btn_row, -1, BUTTON_ROWS,
                &btn_col, 0, BUTTON_COLS);
            break;
#endif
#ifdef DARTS_DOWN
        case DARTS_DOWN:
        case DARTS_RDOWN:
#ifdef HAVE_SCROLLWHEEL
        case PLA_SCROLL_FWD:
        case PLA_SCROLL_FWD_REPEAT:
#endif
            move_with_wrap_and_shift(
                &btn_row, 1, BUTTON_ROWS,
                &btn_col, 0, BUTTON_COLS);
            break;
#endif
        case DARTS_QUIT:
            switch (do_dart_scorer_pause_menu())
            {
            case 0: /* resume */
                break;
            case 1:
                init_game(true);
                continue;
            case 2: /* quit w/o saving */
                rb->remove(RESUME_FILE);
                return PLUGIN_ERROR;
            case 3: /* save & quit */
                save_game();
                return PLUGIN_ERROR;
                break;
            }
            break;
        default:
        {
            exit_on_usb(button); /* handle poweroff and USB */
            break;
        }
        }
        draw();
    }

GAMEOVER:
    rb->splashf(HZ * 3, "Gameover. Player %d wins!", settings.turn + 1);

    return PLUGIN_OK;
}

/* decide if this_item should be shown in the main menu */
/* used to hide resume option when there is no save */
static int mainmenu_cb(int action,
                       const struct menu_item_ex *this_item,
                       struct gui_synclist *this_list)
{
    (void)this_list;
    int idx = ((intptr_t)this_item);
    if (action == ACTION_REQUEST_MENUITEM && !loaded && (idx == 0 || idx == 5))
        return ACTION_EXIT_MENUITEM;
    return action;
}

/* show the pause menu */
static int do_dart_scorer_pause_menu(void)
{
    int sel = 0;
    MENUITEM_STRINGLIST(menu, "Dart Scorer", NULL,
                        "Resume Game",
                        "Start New Game",
                        "Playback Control",
                        "Help",
                        "Quit without Saving",
                        "Quit");
    while (1)
    {
        switch (rb->do_menu(&menu, &sel, NULL, false))
        {
        case 0:
        {
            rb->splash(HZ * 2, "Resume");
            return 0;
        }
        case 1:
        {
            if (!confirm_quit())
                break;
            else
            {
                rb->splash(HZ * 2, "New Game");
                return 1;
            }
        }
        case 2:
            playback_control(NULL);
            break;
        case 3:
            do_help();
            break;
        case 4: /* quit w/o saving */
        {
            if (!confirm_quit())
                break;
            else
            {
                return 2;
            }
        }
        case 5:
            return 3;
        default:
            break;
        }
    }
}

/* show the main menu */
static enum plugin_status do_dart_scorer_menu(void)
{
    int sel = 0;
    loaded = load_game();
    MENUITEM_STRINGLIST(menu,
                        "Dart Scorer",
                        mainmenu_cb,
                        "Resume Game",
                        "Start New Game",
                        "Playback Control",
                        "Help",
                        "Quit without Saving",
                        "Quit");
    while (true)
    {
        switch (rb->do_menu(&menu, &sel, NULL, false))
        {
        case 0: /* Start new game or resume a game */
        case 1:
        {
            if (sel == 1 && loaded)
            {
                if (!confirm_quit())
                    break;
            }
            enum plugin_status ret = do_game(sel == 1);
            switch (ret)
            {
            case PLUGIN_OK:
            {
                loaded = false;
                rb->remove(RESUME_FILE);
                break;
            }
            case PLUGIN_USB_CONNECTED:
                save_game();
                return ret;
            case PLUGIN_ERROR: /* exit without menu */
                return PLUGIN_OK;
            default:
                break;
            }
            break;
        }
        case 2:
            playback_control(NULL);
            break;
        case 3:
            do_help();
            break;
        case 4:
            if (confirm_quit())
                return PLUGIN_OK;
            break;
        case 5:
            if (loaded)
                save_game();
            return PLUGIN_OK;
        default:
            break;
        }
    }
}

/* prepares for exit */
static void cleanup(void)
{
    backlight_use_settings();
}

enum plugin_status plugin_start(const void *parameter)
{
    (void)parameter;
    /* now start the game menu */
    enum plugin_status ret = do_dart_scorer_menu();
    cleanup();
    return ret;
}
