/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2005 Dave Chapman
 *
 * 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.
 *
 ****************************************************************************/

/***
Sudoku by Dave Chapman

User instructions
-----------------

Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
the number under the cursor.

At any time during the game, press On to bring up the game menu with
further options:

 Save
 Reload
 Clear
 Solve

Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
Simple Sudoku and other applications - http://angusj.com/sudoku/

In-progress game positions are saved in the original .ss file, with
A-I used to indicate numbers entered by the user.

Example ".ss" file, and one with a saved state:

...|...|...     ...|...|...
2..|8.4|9.1     2.C|8.4|9.1
...|1.6|32.     E..|1.6|32.
-----------     -----------
...|..5|.4.     ...|..5|.4.
8..|423|..6     8..|423|..6
.3.|9..|...     .3D|9..|A..
-----------     -----------
.63|7.9|...     .63|7.9|...
4.9|5.2|..8     4.9|5.2|.C8
...|...|...     ...|...|...

*/

#include "plugin.h"
#include "lib/configfile.h"

#include <lib/playback_control.h>
#include "sudoku.h"
#include "generator.h"

/* The bitmaps */
#include "pluginbitmaps/sudoku_normal.h"
#include "pluginbitmaps/sudoku_inverse.h"
#include "pluginbitmaps/sudoku_start.h"

#define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
#define BITMAP_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_sudoku_normal, BMPHEIGHT_sudoku_normal)

#if (LCD_DEPTH>2)
#define BITMAP_WIDTH  (BMPWIDTH_sudoku_normal/2)
#else
#define BITMAP_WIDTH  BMPWIDTH_sudoku_normal
#endif



/* Default game - used to initialise sudoku.ss if it doesn't exist. */
static const char default_game[9][9] =
{
    { '0','1','0',  '3','0','7', '0','0','4' },
    { '0','0','0',  '0','6','0', '1','0','2' },
    { '0','0','0',  '0','8','0', '5','6','0' },

    { '0','6','0',  '0','0','0', '0','2','9' },
    { '0','0','0',  '5','0','3', '0','0','0' },
    { '7','9','0',  '0','0','0', '0','3','0' },

    { '0','8','5',  '0','3','0', '0','0','0' },
    { '1','0','2',  '0','7','0', '0','0','0' },
    { '0','0','0',  '4','0','8', '0','5','0' },
};

#if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */

#if ((LCD_HEIGHT==64 && (LCD_WIDTH==112 || LCD_WIDTH==128)) || (LCD_HEIGHT == 96 && LCD_WIDTH == 96))
/* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
#define SMALL_BOARD
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   1   /* Mark width and height */

#elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
/* C200, 9 cells @ 8x8 with 8 border lines */
#define SMALL_BOARD
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   1   /* Mark width and height */

#elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
/* iAudio M3, Samsung YH820, 9 cells @ 9x9 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  2   /* Pixels between two marks */
#define MARK_SIZE   1   /* Mark width and height */

#elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
   || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
/* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
/* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   2   /* Mark width and height */

#elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) \
   || ((LCD_HEIGHT==132) && (LCD_WIDTH==176))
/* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
/* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  2   /* Pixels between two marks */
#define MARK_SIZE   2   /* Mark width and height */

#elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
/* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   4   /* Mark width and height */

#elif (LCD_HEIGHT==240) && (LCD_WIDTH==240)
/* Anbernic RG Nano - 240x240, 9 cells @ 20x20 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   4   /* Mark width and height */

#elif (LCD_HEIGHT==240) && (LCD_WIDTH==320)
/* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  2   /* Pixels between two marks */
#define MARK_SIZE   6   /* Mark width and height */

#elif (LCD_HEIGHT==480) && (LCD_WIDTH==640)
/* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  2   /* Pixels between two marks */
#define MARK_SIZE   6   /* Mark width and height */

#else
  #error SUDOKU: Unsupported LCD size
#endif

#else /* Vertical layout, scratchpad at the bottom */
#define VERTICAL_LAYOUT

#if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
/* e200, 9 cells @ 16x16 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   4   /* Mark width and height */

#elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
/* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  2   /* Pixels between two marks */
#define MARK_SIZE   6   /* Mark width and height */

#elif ((LCD_HEIGHT==160) && (LCD_WIDTH==128))
/* Philips GoGear SA9200 - 128x160, 9 cells @ 10x10 with 14 border tiles */
#define MARK_OFFS   1   /* Pixels between border and mark */
#define MARK_SPACE  1   /* Pixels between two marks */
#define MARK_SIZE   2   /* Mark width and height */

#else
  #error SUDOKU: Unsupported LCD size
#endif

#endif /* Layout */

#define CELL_WIDTH  BITMAP_WIDTH
#define CELL_HEIGHT BITMAP_HEIGHT

#ifdef SUDOKU_BUTTON_CHANGEDIR
int invertdir=0;
#else
#define invertdir 0
#endif

#define CFGFILE_VERSION 0     /* Current config file version */
#define CFGFILE_MINVERSION 0  /* Minimum config file version to accept */

#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
/* settings */
struct sudoku_config {
#ifdef HAVE_LCD_COLOR
    int number_display;
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
    int show_markings;
#endif
};

struct sudoku_config sudcfg_disk = {
#ifdef HAVE_LCD_COLOR
    0,
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
    1,
#endif
};
struct sudoku_config sudcfg;

static const char cfg_filename[] =  "sudoku.cfg";
#ifdef HAVE_LCD_COLOR
static char *number_str[2] = { "black", "coloured" };
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
static char *mark_str[2] = { "hide", "show" };
#endif

struct configdata disk_config[] = {
#ifdef HAVE_LCD_COLOR
   { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
     number_str },
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
   { TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
     mark_str },
#endif
};
#endif
#ifdef HAVE_LCD_COLOR
#define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
#else
#define NUMBER_TYPE 0
#endif

/* Size dependent build-time calculations */
#ifdef SMALL_BOARD
#define BOARD_WIDTH  (CELL_WIDTH*9+10)
#define BOARD_HEIGHT (CELL_HEIGHT*9+10)
static unsigned int cellxpos[9]={
                  1,    (CELL_WIDTH+2), (2*CELL_WIDTH+3),
    (3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
    (6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
};
static unsigned int cellypos[9]={
                   1,    (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
    (3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
    (6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
};
#else /* !SMALL_BOARD */
#define BOARD_WIDTH  (CELL_WIDTH*9+10+4)
#define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
static unsigned int cellxpos[9]={
                   2,    (CELL_WIDTH +3), (2*CELL_WIDTH +4),
    (3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
    (6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
};
static unsigned int cellypos[9]={
                    2,    (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
    (3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
    (6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
};
#endif

#ifdef VERTICAL_LAYOUT
#define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
#define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
#define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
#else
#define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
#define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
#endif

#define BLOCK        3
#define SIZE         (BLOCK*BLOCK)

static void sudoku_solve(struct sudoku_state_t* state)
{
    bool ret = sudoku_solve_board(state);

    if (!ret) {
        rb->splash(HZ*2, "Solve failed");
    }

    return;
}

/* Copies the current to the saved board */
static void save_state(struct sudoku_state_t *state)
{
    rb->memcpy(state->savedboard, state->currentboard,
            sizeof(state->savedboard));
#ifdef SUDOKU_BUTTON_POSSIBLE
    rb->memcpy(state->savedpossible, state->possiblevals,
            sizeof(state->savedpossible));
#endif
}

/* Copies the saved to the current board */
static void restore_state(struct sudoku_state_t *state)
{
    rb->memcpy(state->currentboard, state->savedboard,
            sizeof(state->savedboard));
#ifdef SUDOKU_BUTTON_POSSIBLE
    rb->memcpy(state->possiblevals, state->savedpossible,
            sizeof(state->possiblevals));
#endif
}

static void default_state(struct sudoku_state_t* state)
{
    int r,c;

    rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
    for (r=0;r<9;r++) {
        for (c=0;c<9;c++) {
            state->startboard[r][c]=default_game[r][c];
            state->currentboard[r][c]=default_game[r][c];
#ifdef SUDOKU_BUTTON_POSSIBLE
            state->possiblevals[r][c]=0;
#endif
        }
    }

    /* initialize the saved board so reload function works */
    save_state(state);

    state->x=0;
    state->y=0;
    state->editmode=0;
}

static void clear_state(struct sudoku_state_t* state)
{
    int r,c;

    rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
    for (r=0;r<9;r++) {
        for (c=0;c<9;c++) {
            state->startboard[r][c]='0';
            state->currentboard[r][c]='0';
#ifdef SUDOKU_BUTTON_POSSIBLE
            state->possiblevals[r][c]=0;
#endif
        }
    }

    state->x=0;
    state->y=0;
    state->editmode=0;
}

/* Check the status of the board, assuming a change at the cursor location */
static bool check_status(struct sudoku_state_t* state)
{
    int check[9];
    int r,c;
    int r1,c1;
    int cell;

    /* First, check the column */
    for (cell=0;cell<9;cell++) {
        check[cell]=0;
    }
    for (r=0;r<9;r++) {
        cell=state->currentboard[r][state->x];
        if (cell!='0') {
            if (check[cell-'1']==1) {
                return true;
            }
            check[cell-'1']=1;
        }
    }

    /* Second, check the row */
    for (cell=0;cell<9;cell++) {
        check[cell]=0;
    }
    for (c=0;c<9;c++) {
        cell=state->currentboard[state->y][c];
        if (cell!='0') {
            if (check[cell-'1']==1) {
                return true;
            }
            check[cell-'1']=1;
        }
    }

    /* Finally, check the 3x3 sub-grid */
    for (cell=0;cell<9;cell++) {
        check[cell]=0;
    }
    r1=(state->y/3)*3;
    c1=(state->x/3)*3;
    for (r=r1;r<r1+3;r++) {
        for (c=c1;c<c1+3;c++) {
            cell=state->currentboard[r][c];
            if (cell!='0') {
                if (check[cell-'1']==1) {
                    return true;
                }
                check[cell-'1']=1;
            }
        }
    }

    /* We passed all the checks :) */

    return false;
}

/* Load game - only ".ss" is officially supported, but any sensible
   text representation (one line per row) may load.
*/
static bool load_sudoku(struct sudoku_state_t* state, char* filename)
{
    int fd;
    size_t n;
    int r = 0, c = 0, d = 0;
    unsigned int i;
    int valid=0;
    char buf[500]; /* A buffer to read a sudoku board from */

    fd=rb->open(filename, O_RDONLY);
    if (fd < 0) {
        LOGF("Invalid sudoku file: %s\n",filename);
        return(false);
    }

    rb->strlcpy(state->filename,filename,MAX_PATH);
    n=rb->read(fd,buf,500);
    if (n <= 0) {
        return(false);
    }
    rb->close(fd);
    r=0;
    c=0;
    i=0;
    d=0;
    while ((i < n) && (r < 9)) {
        switch (buf[i]){
            case ' ': case '\t':
                if (c > 0)
                    valid=1;
                break;
            case '|':
            case '*':
            case '-':
            case '\r':
                break;
            case '\n':
                if (valid) {
                    r++;
                    valid=0;
                }
                c = 0;
                d = 0;
                break;
            case '_': case '.':
                valid=1;
                if (c >= SIZE || r >= SIZE){
                    LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
                         c, r);
                    return(false);
                }
                c++;
                break;
            default:
                if (((buf[i]>='A') && (buf[i]<='I')) ||
                    ((buf[i]>='0') && (buf[i]<='9'))) {
                    valid=1;
                    if (r >= SIZE || c >= SIZE){
                        LOGF("ERROR: sudoku problem is the wrong size "
                             "(%d,%d)\n", c, r);
                        return(false);
                    }
                    if ((buf[i]>='0') && (buf[i]<='9')) {
                        state->startboard[r][c]=buf[i];
                        state->currentboard[r][c]=buf[i];
                    } else {
                        state->currentboard[r][c]='1'+(buf[i]-'A');
                    }
                    c++;
                }
                if((buf[i]>='a' && buf[i] <= 'z') && i < (n-1)
                    && (buf[i+1] >= 'a' && buf[i+1] <= 'z')) {
                    state->possiblevals[r][d]
                        = (((buf[i]-'a') * 26 + buf[i+1]-'a')<<1);
                    i++;
                    d++;
                }
                /* Ignore any other characters */
                break;
        }
        i++;
    }

    /* Check that the board is valid - we need to check every row/column
       and block individually */
    for (state->y = 0; state->y < 9; state->y++) {
        state->x = (state->y%3)*3 + (state->y/3);
        if (check_status(state)) return false;
    }
    state->x = 0;
    state->y = 0;

    /* Save a copy of the saved state - so we can reload without using the
       disk */
    save_state(state);
    return(true);
}

static bool save_sudoku(struct sudoku_state_t* state)
{
    int fd;
    int r,c;
    int i;
#ifdef SUDOKU_BUTTON_POSSIBLE
    int x;
    char line[41] __NONSTRING ="...|...|... ;                          \r\n";
#else
    char line[13] __NONSTRING = "...|...|...\r\n";
#endif
    char sep[13]__NONSTRING = "-----------\r\n";

    rb->splash(0, "Saving...");

    if (state->filename[0]==0) {
        return false;
    }

    fd=rb->open(state->filename, O_WRONLY|O_CREAT, 0666);
    if (fd >= 0) {
        for (r=0;r<9;r++) {
            i=0;
            for (c=0;c<9;c++) {
                if (state->startboard[r][c]!='0') {
                    line[i]=state->startboard[r][c];
                } else if (state->currentboard[r][c]!='0') {
                    line[i]='A'+(state->currentboard[r][c]-'1');
                } else {
                    line[i]='.';
                }
                i++;
                if ((c==2) || (c==5)) {
                    i++;
                }
            }
#ifdef SUDOKU_BUTTON_POSSIBLE
            i+=2;
            for(c=0; c<9; c++) {
                x = ((state->possiblevals[r][c]>>1)/26);
                line[i++] = x + 'a';
                x = ((state->possiblevals[r][c]>>1)%26);
                line[i++] = x + 'a';
            }
#endif
            rb->write(fd,line,sizeof(line));
            if ((r==2) || (r==5)) {
                rb->write(fd,sep,sizeof(sep));
            }
        }
        /* Add a blank line at end */
        rb->write(fd,"\r\n",2);
        rb->close(fd);
        rb->reload_directory();
        /* Save a copy of the saved state - so we can reload without
           using the disk */
        save_state(state);
        return true;
    } else {
        return false;
    }
}

static void clear_board(struct sudoku_state_t* state)
{
    int r,c;

    for (r=0;r<9;r++) {
        for (c=0;c<9;c++) {
            state->currentboard[r][c]=state->startboard[r][c];
        }
    }
    state->x=0;
    state->y=0;
}

static void update_cell(struct sudoku_state_t* state, int r, int c)
{
    /* We have four types of cell:
       1) User-entered number
       2) Starting number
       3) Cursor in cell
    */

    if ((r==state->y) && (c==state->x)) {
        rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
                            BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
                            BITMAP_STRIDE,
                            XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
                            CELL_HEIGHT);
    } else {
        if (state->startboard[r][c]!='0') {
            rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
                                BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
                                BITMAP_STRIDE,
                                XOFS+cellxpos[c],YOFS+cellypos[r],
                                CELL_WIDTH,CELL_HEIGHT);
        } else {
            rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
                                BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
                                BITMAP_STRIDE,
                                XOFS+cellxpos[c],YOFS+cellypos[r],
                                CELL_WIDTH,CELL_HEIGHT);
        }
    }

    rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
}


static void display_board(struct sudoku_state_t* state)
{
    int r,c;
#ifdef SUDOKU_BUTTON_POSSIBLE
    int i;
#endif

    /* Clear the display buffer */
    rb->lcd_clear_display();

    /* Draw the gridlines - differently for different targets */

#ifdef SMALL_BOARD
    /* Small targets - draw dotted/single lines */
    for (r=0;r<9;r++) {
        if ((r % 3)==0) {
            /* Solid Line */
            rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
            rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
        } else {
            /* Dotted line */
            for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
                rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
            }
            for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
                rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
            }
        }
    }
    rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
    rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
#else
    /* Large targets - draw single/double lines */
    for (r=0;r<9;r++) {
        rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
        rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
        if ((r % 3)==0) {
            rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
            rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
        }
    }
    rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
    rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
    rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
    rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
#endif

#ifdef SUDOKU_BUTTON_POSSIBLE
#ifdef VERTICAL_LAYOUT
    rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
    rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
    for (r=0;r<9;r++) {
#ifdef SMALL_BOARD
        /* Small targets - draw dotted/single lines */
        if ((r % 3)==0) {
            /* Solid Line */
            rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
                          YOFSSCRATCHPAD+CELL_HEIGHT+1);
        } else {
            /* Dotted line */
            for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
                rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
            }
        }
#else
        /* Large targets - draw single/double lines */
        rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
                      YOFSSCRATCHPAD+CELL_HEIGHT+1);
        if ((r % 3)==0)
            rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
                          YOFSSCRATCHPAD+CELL_HEIGHT+1);
#endif
        if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
            rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
                                BITMAP_STRIDE,XOFS+cellxpos[r-1],
                                YOFSSCRATCHPAD+1,CELL_WIDTH,CELL_HEIGHT);
    }
    rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
                  YOFSSCRATCHPAD+CELL_HEIGHT+1);
#ifndef SMALL_BOARD
    rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
                  YOFSSCRATCHPAD+CELL_HEIGHT+1);
#endif
    if (state->possiblevals[state->y][state->x]&BIT_N(r))
        rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
                            BITMAP_STRIDE,XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
                            CELL_WIDTH,CELL_HEIGHT);
#else /* Horizontal layout */
    rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
    rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
    for (r=0;r<9;r++) {
#ifdef SMALL_BOARD
        /* Small targets - draw dotted/single lines */
        if ((r % 3)==0) {
            /* Solid Line */
            rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
                          YOFS+cellypos[r]-1);
        } else {
            /* Dotted line */
            for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
                rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
            }
        }
#else
        /* Large targets - draw single/double lines */
        rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
                      YOFS+cellypos[r]-1);
        if ((r % 3)==0)
            rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
                          YOFS+cellypos[r]-2);
#endif
        if ((r>0) && state->possiblevals[state->y][state->x]&BIT_N(r))
            rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
                                BITMAP_STRIDE,XOFSSCRATCHPAD+1,
                                YOFS+cellypos[r-1],CELL_WIDTH,CELL_HEIGHT);
    }
    rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
                  YOFS+cellypos[8]+CELL_HEIGHT);
#ifndef SMALL_BOARD
    rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
                  YOFS+cellypos[8]+CELL_HEIGHT+1);
#endif
    if (state->possiblevals[state->y][state->x]&BIT_N(r))
        rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,BITMAP_HEIGHT*r,
                            BITMAP_STRIDE,XOFSSCRATCHPAD+1,YOFS+cellypos[8],
                            CELL_WIDTH,CELL_HEIGHT);
#endif /* Layout */
#endif /* SUDOKU_BUTTON_POSSIBLE */

    /* Draw the numbers */
    for (r=0;r<9;r++) {
        for (c=0;c<9;c++) {
            /* We have four types of cell:
               1) User-entered number
               2) Starting number
               3) Cursor in cell
            */

            if ((r==state->y) && (c==state->x)) {
                rb->lcd_bitmap_part(sudoku_inverse,NUMBER_TYPE,
                                    BITMAP_HEIGHT*(state->currentboard[r][c]-
                                                   '0'),
                                    BITMAP_STRIDE,
                                    XOFS+cellxpos[c],YOFS+cellypos[r],
                                    CELL_WIDTH,CELL_HEIGHT);
            } else {
                if (state->startboard[r][c]!='0') {
                    rb->lcd_bitmap_part(sudoku_start,NUMBER_TYPE,
                                        BITMAP_HEIGHT*(state->startboard[r][c]-
                                                       '0'),
                                        BITMAP_STRIDE,
                                        XOFS+cellxpos[c],YOFS+cellypos[r],
                                        CELL_WIDTH,CELL_HEIGHT);
                } else {
                    rb->lcd_bitmap_part(sudoku_normal,NUMBER_TYPE,
                                        BITMAP_HEIGHT*
                                        (state->currentboard[r][c]-'0'),
                                        BITMAP_STRIDE,
                                        XOFS+cellxpos[c],YOFS+cellypos[r],
                                        CELL_WIDTH,CELL_HEIGHT);
                }

#ifdef SUDOKU_BUTTON_POSSIBLE
                /* Draw the possible number markings on the board */
                if(sudcfg.show_markings && state->startboard[r][c]=='0'
                    && state->currentboard[r][c]=='0') {
                    for(i=0;i<9;i++) {
                        if(state->possiblevals[r][c]&(2<<i)) {
#if LCD_DEPTH > 1
                            /* draw markings in dark grey */
                            rb->lcd_set_foreground(LCD_DARKGRAY);
#endif
                            rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
                                             +(i%3)*(MARK_SIZE+MARK_SPACE),
                                             YOFS+cellypos[r]+MARK_OFFS
                                             +(i/3)*(MARK_SIZE+MARK_SPACE),
                                             MARK_SIZE,
                                             MARK_SIZE);
#if LCD_DEPTH > 1
                            rb->lcd_set_foreground(LCD_BLACK);
#endif
                        }
                    }
                }
#endif /* SUDOKU_BUTTON_POSSIBLE */
            }
        }
    }

    /* update the screen */
    rb->lcd_update();
}

static bool sudoku_generate(struct sudoku_state_t* state)
{
    char* difficulty;
    char str[80];
    bool res;
    struct sudoku_state_t new_state;

    clear_state(&new_state);
    display_board(&new_state);
    rb->splash(0, "Generating...");

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

    res = sudoku_generate_board(&new_state,&difficulty);

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

    if (res) {
        rb->memcpy(state,&new_state,sizeof(new_state));
        rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
        display_board(state);
        rb->splash(HZ*3, str);
        rb->strlcpy(state->filename,GAME_FILE,MAX_PATH);
    } else {
        display_board(&new_state);
        rb->splash(HZ*2, "Aborted");
    }
    /* initialize the saved board so reload function works */
    save_state(state);
    return res;
}

#ifdef HAVE_LCD_COLOR
static bool numdisplay_setting(void)
{
    static const struct opt_items names[] = {
        {"Black",  -1},
        {"Coloured",  -1},
    };

    return rb->set_option("Number Display", &sudcfg.number_display, RB_INT, names,
                          sizeof(names) / sizeof(names[0]), NULL);
}
#endif

#ifdef SUDOKU_BUTTON_POSSIBLE
static bool showmarkings_setting(void)
{
    static const struct opt_items names[] = {
        {"Hide",  -1},
        {"Show",  -1},
    };

    return rb->set_option("Show Markings", &sudcfg.show_markings, RB_INT, names,
                          sizeof(names) / sizeof(names[0]), NULL);
}
#endif

enum {
    SM_AUDIO_PLAYBACK = 0,
#ifdef HAVE_LCD_COLOR
    SM_NUMBER_DISPLAY,
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
    SM_SHOW_MARKINGS,
#endif
    SM_SAVE,
    SM_RELOAD,
    SM_CLEAR,
    SM_SOLVE,
    SM_GENERATE,
    SM_NEW,
    SM_QUIT,
};

static int sudoku_menu(struct sudoku_state_t* state)
{
    int result;

    MENUITEM_STRINGLIST(menu, "Sudoku", NULL,
                        "Audio Playback",
#ifdef HAVE_LCD_COLOR
                        "Number Display",
#endif
#ifdef SUDOKU_BUTTON_POSSIBLE
                        "Show Markings",
#endif
                        "Save", "Reload", "Clear", "Solve",
                        "Generate", "New", "Quit");

    result = rb->do_menu(&menu, NULL, NULL, false);

    switch (result) {
        case SM_AUDIO_PLAYBACK:
            playback_control(NULL);
            break;

#ifdef HAVE_LCD_COLOR
        case SM_NUMBER_DISPLAY:
            numdisplay_setting();
            break;
#endif

#ifdef SUDOKU_BUTTON_POSSIBLE
        case SM_SHOW_MARKINGS:
            showmarkings_setting();
            break;
#endif
        case SM_SAVE:
            save_sudoku(state);
            break;

        case SM_RELOAD:
            restore_state(state);
            break;

        case SM_CLEAR:
            clear_board(state);
            break;

        case SM_SOLVE:
            sudoku_solve(state);
            break;

        case SM_GENERATE:
            sudoku_generate(state);
            break;

        case SM_NEW:
            clear_state(state);
            state->editmode=1;
            break;

        case SM_QUIT:
            save_sudoku(state);
            break;

        default:
            break;
    }

    return result;
}

/* Menu used when user is in edit mode - i.e. creating a new game manually */
static int sudoku_edit_menu(struct sudoku_state_t* state)
{
    int result;

    MENUITEM_STRINGLIST(menu, "Edit", NULL,
                        "Save as", "Quit");

    result = rb->do_menu(&menu, NULL, NULL, false);

    switch (result) {
        case 0: /* Save new game */
            rb->kbd_input(state->filename,MAX_PATH, NULL);
            if (save_sudoku(state)) {
                state->editmode=0;
            } else {
                rb->splash(HZ*2, "Save failed");
            }
            break;

        case 1: /* Quit */
            break;

        default:
            break;
    }

    return result;
}

static void move_cursor(struct sudoku_state_t* state, int newx, int newy)
{
    int oldx, oldy;

    /* Check that the character at the cursor position is legal */
    if (check_status(state)) {
        rb->splash(HZ*2, "Illegal move!");
        /* Ignore any button presses during the splash */
        rb->button_clear_queue();
        return;
    }

    /* Move Cursor */
    oldx=state->x;
    oldy=state->y;
    state->x=newx;
    state->y=newy;

    /* Redraw current and old cells */
    update_cell(state,oldx,oldy);
    update_cell(state,newx,newy);
}

/* plugin entry point */
enum plugin_status plugin_start(const void* parameter)
{
    bool exit;
    int button;
#if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
    int lastbutton = BUTTON_NONE;
#endif
    int res;
    int rc = PLUGIN_OK;
    long ticks;
    struct sudoku_state_t state;

#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
    configfile_load(cfg_filename, disk_config,
                    sizeof(disk_config) / sizeof(disk_config[0]),
                    CFGFILE_MINVERSION);
    rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
#endif

#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
    rb->lcd_set_foreground(LCD_BLACK);
    rb->lcd_set_background(LCD_WHITE);
#endif

    clear_state(&state);

    if (parameter==NULL) {
        /* We have been started as a plugin - try default sudoku.ss */
        if (!load_sudoku(&state,GAME_FILE)) {
            /* No previous game saved, use the default */
            default_state(&state);
        }
    } else {
        if (!load_sudoku(&state,(char*)parameter)) {
            rb->splash(HZ*2, "Load error");
            return(PLUGIN_ERROR);
        }
    }


    display_board(&state);

    /* The main game loop */
    exit=false;
    ticks=0;
    while(!exit) {
        button = rb->button_get(true);

        switch(button){
#ifdef SUDOKU_BUTTON_QUIT
            /* Exit game */
            case SUDOKU_BUTTON_QUIT:
                if (check_status(&state)) {
                    rb->splash(HZ*2, "Illegal move!");
                    /* Ignore any button presses during the splash */
                    rb->button_clear_queue();
                } else {
                    save_sudoku(&state);
                    exit=true;
                }
                break;
#endif

      /* Increment digit */
#ifdef SUDOKU_BUTTON_ALTTOGGLE
            case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
#endif
            case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
                /* Slow down the repeat speed to 1/3 second */
                if ((*rb->current_tick-ticks) < (HZ/3)) {
                    break;
                }

#ifdef SUDOKU_BUTTON_ALTTOGGLE
            case SUDOKU_BUTTON_ALTTOGGLE:
#endif
            case SUDOKU_BUTTON_TOGGLE:
#ifdef SUDOKU_BUTTON_TOGGLE_PRE
                if ((button == SUDOKU_BUTTON_TOGGLE)
                    && (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
                    break;
#endif
                /* Increment digit */
                ticks=*rb->current_tick;
                if (state.editmode) {
                    if (state.startboard[state.y][state.x]=='9') {
                        state.startboard[state.y][state.x]='0';
                        state.currentboard[state.y][state.x]='0';
                    } else {
                        state.startboard[state.y][state.x]++;
                        state.currentboard[state.y][state.x]++;
                    }
                } else {
                    if (state.startboard[state.y][state.x]=='0') {
                        if (state.currentboard[state.y][state.x]=='9') {
                            state.currentboard[state.y][state.x]='0';
                        } else {
                            state.currentboard[state.y][state.x]++;
                        }
                    }
                }
                update_cell(&state,state.y,state.x);
                break;

#ifdef SUDOKU_BUTTON_TOGGLEBACK
            case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
                /* Slow down the repeat speed to 1/3 second */
                if ((*rb->current_tick-ticks) < (HZ/3)) {
                    break;
                }

            case SUDOKU_BUTTON_TOGGLEBACK:
                /* Decrement digit */
                ticks=*rb->current_tick;
                if (state.editmode) {
                    if (state.startboard[state.y][state.x]=='0') {
                        state.startboard[state.y][state.x]='9';
                        state.currentboard[state.y][state.x]='9';
                    } else {
                        state.startboard[state.y][state.x]--;
                        state.currentboard[state.y][state.x]--;
                    }
                } else {
                    if (state.startboard[state.y][state.x]=='0') {
                        if (state.currentboard[state.y][state.x]=='0') {
                            state.currentboard[state.y][state.x]='9';
                        } else {
                            state.currentboard[state.y][state.x]--;
                        }
                    }
                }
                update_cell(&state,state.y,state.x);
                break;
#endif

                /* move cursor left */
            case SUDOKU_BUTTON_LEFT:
            case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
                if ( (state.x==0&&invertdir==0) || (state.y==0&&invertdir==1) ) {
#ifndef SUDOKU_BUTTON_UP
                    if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
                        move_cursor(&state,8,8);
                    } else {
                        if (invertdir==0) {
                            move_cursor(&state,8,state.y-1);
                        } else {
                            move_cursor(&state,state.x-1,8);
                        }
                    }
#else
                    move_cursor(&state,8,state.y);
#endif
                } else {
                    if (invertdir==0) {
                        move_cursor(&state,state.x-1,state.y);
                    } else {
                        move_cursor(&state,state.x,state.y-1);
                    }
                }
                break;

                /* move cursor right */
            case SUDOKU_BUTTON_RIGHT:
            case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
                if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
#ifndef SUDOKU_BUTTON_DOWN
                    if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
                        move_cursor(&state,0,0);
                    } else {
                        if (invertdir==0) {
                            move_cursor(&state,0,state.y+1);
                        } else {
                            move_cursor(&state,state.x+1,0);
                        }
                    }
#else
                    move_cursor(&state,0,state.y);
#endif
                } else {
                    if (invertdir==0) {
                        move_cursor(&state,state.x+1,state.y);
                    } else {
                        move_cursor(&state,state.x,state.y+1);
                    }
                }
                break;

#ifdef SUDOKU_BUTTON_UP
                /* move cursor up */
            case SUDOKU_BUTTON_UP:
            case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
                if (state.y==0) {
                    move_cursor(&state,state.x,8);
                } else {
                    move_cursor(&state,state.x,state.y-1);
                }
                break;
#endif

#ifdef SUDOKU_BUTTON_DOWN
                /* move cursor down */
            case SUDOKU_BUTTON_DOWN:
            case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
                if (state.y==8) {
                    move_cursor(&state,state.x,0);
                } else {
                    move_cursor(&state,state.x,state.y+1);
                }
                break;
#endif

            case SUDOKU_BUTTON_MENU:
#ifdef SUDOKU_BUTTON_MENU_PRE
                if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
                    break;
#endif
                /* Don't let the user leave a game in a bad state */
                if (check_status(&state)) {
                    rb->splash(HZ*2, "Illegal move!");
                    /* Ignore any button presses during the splash */
                    rb->button_clear_queue();
                } else {
                    if (state.editmode) {
                        res = sudoku_edit_menu(&state);
                        if (res == MENU_ATTACHED_USB) {
                            rc = PLUGIN_USB_CONNECTED;
                            exit = true;
                        } else if (res == 1) { /* Quit */
                            exit = true;
                        }
                    } else {
                        res = sudoku_menu(&state);
                        if (res == MENU_ATTACHED_USB) {
                            rc = PLUGIN_USB_CONNECTED;
                            exit = true;
                        } else if (res == SM_QUIT) {
                            exit = true;
                        }
                    }
                }
                break;
#ifdef SUDOKU_BUTTON_POSSIBLE
            case SUDOKU_BUTTON_POSSIBLE:
                /* Toggle current number in the possiblevals structure */
                if (state.currentboard[state.y][state.x]!='0') {
                    state.possiblevals[state.y][state.x]^=
                        BIT_N(state.currentboard[state.y][state.x] - '0');
                }
                break;
#endif

#ifdef SUDOKU_BUTTON_CHANGEDIR
            case SUDOKU_BUTTON_CHANGEDIR:
                /* Change scroll wheel direction */
                invertdir=!invertdir;
                break;
#endif
            default:
                if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
                    /* Quit if USB has been connected */
                    rc = PLUGIN_USB_CONNECTED;
                    exit = true;
                }
                break;
        }
#if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
        if (button != BUTTON_NONE)
            lastbutton = button;
#endif

        display_board(&state);
    }
#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
    if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
    {
        rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
        configfile_save(cfg_filename, disk_config,
                        sizeof(disk_config) / sizeof(disk_config[0]),
                        CFGFILE_VERSION);
    }
#endif
    return rc;
}
