/* 
.--
|__
|  IFTEEN

Authored by abakh <abakh@tuta.io>
No rights are reserved and this software comes with no warranties of any kind to the extent permitted by law.

compile with -lncurses
*/
#include <curses.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>
#include <signal.h>
#include "config.h"
typedef signed char byte;

/* The Plan9 compiler can not handle VLAs */
#ifdef NO_VLA
#define size 4
#else
byte size;
#endif
byte py,px;
byte ey,ex; //the empty tile
chtype green=A_BOLD; //bold when there is no color
void rectangle(byte sy,byte sx){
	for(byte y=0;y<=size+1;++y){
		mvaddch(sy+y,sx,ACS_VLINE);
		mvaddch(sy+y,sx+size*2,ACS_VLINE);
	}
	for(byte x=0;x<=size*2;++x){
		mvaddch(sy,sx+x,ACS_HLINE);
		mvaddch(sy+size+1,sx+x,ACS_HLINE);
	}
	mvaddch(sy,sx,ACS_ULCORNER);
	mvaddch(sy+size+1,sx,ACS_LLCORNER);
	mvaddch(sy,sx+size*2,ACS_URCORNER);
	mvaddch(sy+size+1,sx+size*2,ACS_LRCORNER);
}
void logo(byte sy,byte sx){
	mvaddstr(sy,sx,  ".--");
	mvaddstr(sy+1,sx,"|__");
	mvaddstr(sy+2,sx,"|  IFTEEN");
}
//convert integer to representing sign
char int2sgn(byte num){
	if(!num)
		return ' ';
	else if(0< num && num <= 9)
		return num+'0';
	else if(10<=num && num <=35)
		return num-10+'a';
	else if(36<=num && num <=51)
		return num-36+'A';
	return 0;
}
/*bool isinorder(byte board[size][size],byte y,byte x){ using check[][] is much cheaper
	return (board[y][x] == y*size+x+1);
} */

//display
void draw(byte sy,byte sx,char board[size][size],char check[size][size]){
	rectangle(sy,sx);
	chtype prnt;
	byte y,x;
	for(y=0;y<size;++y){
		for(x=0;x<size;++x){
			prnt=board[y][x];
			if(check[y][x]==board[y][x] && check[y][x] != ' ')
				prnt |= green;
			if(y==py && x==px)
				prnt |= A_STANDOUT;
			mvaddch(sy+1+y,sx+x*2+1,prnt);
		}
	}
}
void fill(char board[size][size]){
	byte y,x;
	for(y=0;y<size;++y){
		for(x=0;x<size;++x){
			board[y][x]= int2sgn(y*size+x+1);
		}
	}
	board[size-1][size-1]=' ';
}
void slide_one(char board[size][size],byte y,byte x){
	if( (y>=0 && y<size && x>=0 && x<size) && ((abs(y-ey)==1)^(abs(x-ex)==1)) ){
		board[ey][ex]=board[y][x];
		board[y][x]=' ';
		ey=y;//ey/x moves one tile
		ex=x;
	}
}
void slide_multi(char board[size][size],byte y,byte x){
	byte dy,dx;
	dy=dx=0;
	if( (ey==y) ^ (ex==x) ){
		if(ey!=y)//d's are steps from ey/x to y/x
			 dy=(y-ey >0)? 1:-1;
		if(ex!=x)
			 dx=(x-ex >0)? 1:-1;
		while(ex!=x || ey!=y){
			slide_one(board,ey+dy,ex+dx);//ey/x comes forth itself
		}
		ey=y;
		ex=x;
	}
}
bool issolved(char board[size][size],char check[size][size]){
	byte y,x;
	for(y=0;y<size;++y){
		for(x=0;x<size;++x){
			if(board[y][x]!=check[y][x])
				return 0;
		}
	}
	return 1;
}
void shuffle(char board[size][size]){
	for(int m=0;m<10000;++m){
		switch(rand()%4){
			case 0:
				slide_one(board,ey,ex+1);
				break;
			case 1:
				slide_one(board,ey,ex-1);
				break;
			case 2:
				slide_one(board,ey+1,ex);
				break;
			case 3:
				slide_one(board,ey-1,ex);
				break;
		}
	}
	refresh();
	getch();
}
//peacefully close when ^C is pressed
void sigint_handler(int x){
	endwin();
	puts("Quit.");
	exit(x);
}
void mouseinput(void){
#ifndef NO_MOUSE
	MEVENT minput;
	#ifdef PDCURSES
	nc_getmouse(&minput);
	#else
	getmouse(&minput);
	#endif
	if( minput.y-4<size && minput.x-1<size*2){
		py=minput.y-4;
		px=(minput.x-1)/2;
	}
	else
		return;
	if(minput.bstate & BUTTON1_CLICKED)
		ungetch('\n');
#endif
}
void help(void){
	erase();
	logo(0,0);
	attron(A_BOLD);
	mvprintw(3,0,"  **** THE CONTROLS ****");
	mvprintw(8,0,"YOU CAN ALSO USE THE MOUSE!");
	attroff(A_BOLD);
	mvprintw(4,0,"RETURN/ENTER : Slide");
	mvprintw(5,0,"hjkl/ARROW KEYS : Move cursor");
	mvprintw(6,0,"q : Quit");
	mvprintw(7,0,"F1 & F2 : Help on controls & gameplay");
	mvprintw(10,0,"Press a key to continue");
	refresh();
	getch();
	erase();
}
void gameplay(void){
	erase();
	logo(0,0);
	attron(A_BOLD);
	mvprintw(3,0,"  **** THE GAMEPLAY ****");
	attroff(A_BOLD);
	mvprintw(4,0,"Slide the tiles until the numbers and characters are\n");
	printw("in the right order.\n");
	refresh();
	getch();
	erase();
}
int main(int argc, char** argv){
#ifndef NO_VLA
	size=4;
	if(argc==2){
		if(!strcmp("help",argv[1])){
			printf("Usage: %s [size]\n",argv[0]);
			return EXIT_SUCCESS;
		}
		size=atoi(argv[1]);
		if(size<3 || size>7){
			fprintf(stderr,"3<=size<=7\n");
			return EXIT_FAILURE;
		}
	}
#endif
	signal(SIGINT,sigint_handler);
	srand(time(NULL)%UINT_MAX);
	initscr();
#ifndef NO_MOUSE
	mousemask(ALL_MOUSE_EVENTS,NULL);
#endif
	noecho();
	cbreak();
	keypad(stdscr,1);
	if(has_colors()){
		start_color();
		use_default_colors();
		init_pair(1,COLOR_GREEN,-1);
		green=COLOR_PAIR(1);
	}
	char board[size][size];
	char check[size][size];
	fill(check);
	int input;
	Start:
	py=px=0;
	ey=ex=size-1;
	curs_set(0);
	fill(board);
	shuffle(board);
	while(1){
		erase();
		logo(0,0);
		draw(3,0,board,check);
		refresh();
		if(issolved(board,check))
			break;
		input = getch();
		if( input==KEY_F(1) || input=='?' )
			help();
		if( input==KEY_F(2) )
			gameplay();
		if( input==KEY_MOUSE )
			mouseinput();
		if( (input=='k' || input==KEY_UP) && py>0)
			--py;
		if( (input=='j' || input==KEY_DOWN) && py<size-1)
			++py;
		if( (input=='h' || input==KEY_LEFT) && px>0)
			--px;
		if( (input=='l' || input==KEY_RIGHT) && px<size-1)
			++px;
		if( input=='q')
			sigint_handler(0);
		if(input=='\n'||input==KEY_ENTER){
			slide_multi(board,py,px);
		}
	}
	mvprintw(size+5,0,"You solved it! Wanna play again?(y/n)");
	curs_set(1);
	input=getch();
	if(input != 'N' && input != 'n' && input != 'q')
		goto Start;
	endwin();
	return EXIT_SUCCESS;
}