/* code from mark huckvale: http://www.phon.ucl.ac.uk/home/mark/sudoku/ */

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

#include "sudoku.h"

char *imgdir = "/sys/games/lib/sudoku/images";
char *lvldir = "/sys/games/lib/sudoku/boards";  /* level library dir */

int selected;   /* which digit do we have selected? */

Image *background;      /* DPaleyellow */
Image *backselect;      /* DPalebluegreen */
Image *blink;           /* DDarkyellow */
Image *brdr;            /* 0x55555555 */
Image *fixed;           /* DBlue */
Image *wrong;           /* DRed */
Image *dig[10];         /* digit masks */

Dir *dir;
int numlevels;
int curlevel;

char *buttons[] =
{
       "new",
       "check",
       "solve",
       "clear",
       "save",
       "load",
       "print",
       "offline",
       "exit",
       0
};

Menu menu =
{
       buttons
};

Menu lmenu =
{
       nil,
       genlevels,
       0,
};

int
readlevels(char *leveldir)
{
       int fd, n;

       if((fd = open(leveldir, OREAD)) < 0)
               return -1;

       n = dirreadall(fd, &dir);
       close(fd);

       return n;
}

char *
genlevels(int i)
{
       if(numlevels == 0)
               numlevels = readlevels(lvldir);

       if(numlevels > 0 && i < numlevels)
               return (dir+i)->name;

       return nil;
}

void
convert(Cell *brd, int *board)
{
       int i;

       for(i = 0; i < Psize; i++) {
               brd[i].digit = board[i] & Digit;
               if(brd[i].digit < 0 || brd[i].digit > 9)
                       brd[i].digit = -1;
               brd[i].solve = (board[i] & Solve) >> 4;
               brd[i].locked = board[i] & MLock;
       }
       memcpy(obrd, brd, Psize * sizeof(Cell));
}

Image *
eallocimage(Rectangle r, int repl, uint color)
{
       Image *tmp;

       tmp = allocimage(display, r, screen->chan, repl, color);
       if(tmp == nil)
               sysfatal("cannot allocate buffer image: %r");

       return tmp;
}

Image *
eloadfile(char *path)
{
       Image *img;
       int fd;

       fd = open(path, OREAD);
       if(fd < 0) {
               fprint(2, "cannot open image file %s: %r\n", path);
               exits("image");
       }
       img = readimage(display, fd, 0);
       if(img == nil)
               sysfatal("cannot load image: %r");
       close(fd);

       return img;
}


void
clearboard(Cell *board)
{
       int i;

       for(i = 0; i < Psize; i++) {
               board[i].digit = -1;
               board[i].solve = 0;
               board[i].locked = 0;
       }
}

void
solveboard(Cell *board)
{
       int i;

       for(i = 0; i < Psize; i++) {
               board[i].digit = board[i].solve;
       }
}


int
checkpossible(Cell *board, int x, int y, int num)
{
       int i, j;

       for(i = 0; i < Brdsize; i++) {
               if(board[i*Brdsize + y].digit == num && i != x)
                       return 0;
               if(board[x*Brdsize + i].digit == num && i != y)
                       return 0;
       }

       for(i = x - (x%3); i < x - (x%3) + 3; i++)
               for(j = y - (y%3); j < y - (y%3) + 3; j++)
                       if((i != x && j != y) && board[i*Brdsize + j].digit == num)
                               return 0;

       return 1;
}

void
resize(void)
{
       int fd;

       fd = open("/dev/wctl", OWRITE);
       if(fd >= 0){
               fprint(fd, "resize -dx %d -dy %d", Maxx, Maxy);
               close(fd);
       }

}

void
drawcell(int x, int y, int num, Image *col)
{
       Rectangle r = Rect(x*Square, y*Square, (x+1)*Square, (y+1)*Square);

       if(num < 0 || num > 9)
               return;

       r = insetrect(r, Border);
       r = rectaddpt(r, Pt(0, Square));
       r.max = addpt(r.max, Pt(2, 2));

       draw(screen, rectaddpt(r, screen->r.min), col, dig[num], ZP);
}

void
drawboard(void)
{
       int i;

       for(i = 0; i < Psize; i++) {
               drawcell(i / Brdsize, i % Brdsize, brd[i].digit, brd[i].locked ? fixed : display->black);
       }
}

void
drawchecked(Cell *brd)
{
       int i;

       for(i = 0; i < Psize; i++) {
               if(brd[i].locked)
                       drawcell(i / Brdsize, i % Brdsize, brd[i].digit, fixed);
               else
                       drawcell(i / Brdsize, i % Brdsize, brd[i].digit,
                                       checkpossible(brd, i / Brdsize, i % Brdsize, brd[i].digit) ? display->black : wrong);
       }
}

void
drawscreen(void)
{
       Point l1, l2;
       int i;

       draw(screen, screen->r, brdr, nil, ZP);
       draw(screen, insetrect(screen->r, Border), background, nil, ZP);
       for(i = 0; i < Brdsize; i++) {
               l1 = addpt(screen->r.min, Pt(i*Square, Square));
               l2 = addpt(screen->r.min, Pt(i*Square, Maxy));
               line(screen, l1, l2, Endsquare, Endsquare, (i%3) == 0 ? Thickline : Line, brdr, ZP);
               l1 = addpt(screen->r.min, Pt(0, (i+1)*Square));
               l2 = addpt(screen->r.min, Pt(Maxx, (i+1)*Square));
               line(screen, l1, l2, Endsquare, Endsquare, (i%3) == 0 ? Thickline : Line, brdr, ZP);
       }
       for(i = 1; i < 10; i++) {
               drawbar(i, (selected == i) ? 1 : 0);
       }
       drawboard();
       flushimage(display, 1);
}


void
drawbar(int digit, int selected)
{
       Rectangle r = Rect((digit - 1)*Square, 0, digit*Square, Square);

       if(digit < 1 || digit > 9)
               return;

       r = insetrect(r, Border);
       r.max = addpt(r.max, Pt(2, 2));
       draw(screen, rectaddpt(r, screen->r.min), selected ? backselect : background, nil, ZP);
       draw(screen, rectaddpt(r, screen->r.min), display->black, dig[digit-1], ZP);
}

void
eresized(int new)
{
       Point p;
       char path[256];
       int i;

       if(new && getwindow(display, Refnone) < 0)
               sysfatal("can't reattach to window");

       if(background == nil)
               background = eallocimage(Rect(0, 0, 1, 1), 1, DPaleyellow);
       if(backselect == nil)
               backselect = eallocimage(Rect(0, 0, 1, 1), 1, DPalebluegreen);
       if(blink == nil)
               blink = eallocimage(Rect(0, 0, 1, 1), 1, DDarkyellow);
       if(brdr == nil)
               brdr = eallocimage(Rect(0, 0, 1, 1), 1, 0x55555555);
       if(fixed == nil)
               fixed = eallocimage(Rect(0, 0, 1, 1), 1, DBlue);
       if(wrong == nil)
               wrong = eallocimage(Rect(0, 0, 1, 1), 1, DRed);
       if(dig[0] == nil) {
               for(i = 0; i < 9; i++) {
                       snprint(path, 256, "%s/%d.bit", imgdir, i+1);
                       dig[i] = eloadfile(path);
               }
       }

       p = Pt(Dx(screen->r), Dy(screen->r));
       if(!new || !eqpt(p, Pt(Maxx - 8, Maxy - 8)))
               resize();

       drawscreen();
}

void
main(int argc, char *argv[])
{
       Mouse m;
       Event e;
       Point p;
       int last1 = 0;  /* was the button clicked last time? */

       USED(argc, argv);

       if(initdraw(nil, nil, "sudoku") < 0)
               sysfatal("initdraw failed: %r");

       einit(Emouse|Ekeyboard);


       clearboard(brd);
       eresized(0);

       srand(time(0)*getpid());
       makep();
       convert(brd, board);

       drawscreen();
       for(;;) {
               switch(event(&e)) {
               case Emouse:
                       m = e.mouse;
                       if(m.buttons&1) {
                               if(last1 == 0) {
                                       last1 = 1;
                                       p = subpt(m.xy, screen->r.min);
                                       if(ptinrect(p, Rect(0, 0, Maxx, Square+Border))) {
                                               if(p.x/Square == selected - 1) {
                                                       drawbar(selected, 0);
                                                       selected = 0;
                                               } else {
                                                       selected = p.x/Square + 1;
                                               }
                                       } else {
                                               Point lp = divpt(p, Square);
                                               lp.y--;

                                               if(brd[lp.x * Brdsize + lp.y].locked)
                                                       break;

                                               if(selected) {
                                                       brd[lp.x * Brdsize + lp.y].digit = selected - 1;
                                               } else {
                                                       brd[lp.x * Brdsize + lp.y].digit = -1;
                                               }
                                       }
                                       drawscreen();
                               }
                       } else {
                               last1 = 0;
                       }

                       if(m.buttons&2) {
                               char *str;
                               int l;
                               /* levels start from 1 */
                               lmenu.lasthit = curlevel;
                               l = emenuhit(2, &m, &lmenu);
                               if(l >= 0){
                                       curlevel = l;
                                       str = smprint("%s/%s", lvldir, (dir+curlevel)->name);
                                       if(loadlevel(str, brd) < 0)
                                               clearboard(brd);
                                       memcpy(obrd, brd, Psize * sizeof(Cell));
                                       free(str);
                               }
                               drawscreen();
                       }
                       if(m.buttons&4) {
                               switch(emenuhit(3, &m, &menu)) {
                               case 0:         /* new */
                                       makep();
                                       convert(brd, board);
                                       drawscreen();
                                       break;
                               case 1:         /* solve */
                                       drawchecked(brd);
                                       break;
                               case 2:         /* solve */
                                       solveboard(brd);
                                       drawscreen();
                                       break;
                               case 3:         /* clear */
                                       memcpy(brd, obrd, Psize * sizeof(Cell));
                                       drawscreen();
                                       break;
                               case 4:         /* save */
                                       savegame(brd);
                                       drawscreen();
                                       break;
                               case 5:         /* load */
                                       if(loadgame(brd) < 0) {
                                               clearboard(brd);
                                       }
                                       memcpy(obrd, brd, Psize * sizeof(Cell));
                                       drawscreen();
                                       break;
                               case 6:         /* print */
                                       printboard(brd);
                                       break;
                               case 7:         /* offline */
                                       fprettyprintbrd(brd);
                                       break;
                               case 8:         /* exit */
                                       exits(nil);
                               }
                       }
                       break;

               case Ekeyboard:
                       switch(e.kbdc) {
                       case 127:
                       case 'q':
                       case 'Q':
                               exits(nil);
                       default:
                               break;
                       }
                       break;
               }
       }
}