/*
 *  Copyright (C) 2007 Jarek 'Jarcec' Cecho <jarcec@jarcec.net>
 *
 *  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 program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; see the file COPYING.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */

// Includes
#include <ncurses.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>

// Set of macros
#define DEF_MAXX 25
#define DEF_MAXY 25
#define DEF_MINES 125

#define POSX(resx, maxx, x) (resx)/2-(maxx)+(x)*2
#define POSY(resy, maxy, y) (resy)/2-(maxy)/2+(y)

#define MPOSX(x) POSX(resx, MAXX, (x))
#define MPOSY(y) POSY(resy, MAXY, (y))

#define KEY_ESCAPE	27
#define KEY_SPACE	32
#ifdef KEY_ENTER
#undef KEY_ENTER
#define KEY_ENTER	10
#endif

// Globalni promene
int MAXX = DEF_MAXX;
int MAXY = DEF_MAXY;
char **pole;		// Mine fild
char **mask;		// Mask for handling marks mines and showing unused parts
int mines_count = DEF_MINES;	// Count of mines (maximum)
int mines = 0;			// Count of founded mines
int resx, resy;			// Resolution of screen

/* Generates the mine fild into global variable pole */
void mines_generate_field()
{
	// variables
	int counter;
	int x, y;

	// Nullize the field
	for(x = 0; x < MAXX; x++)
		for(y = 0; y < MAXY; y++)
			pole[x][y] = 0;

	// Nullize the mask
	for(x = 0; x < MAXX; x++)
		for(y = 0; y < MAXY; y++)
			mask[x][y] = 0;

	// Generating mines
	for(counter = 0; counter < mines_count; counter++)
	{
		// Be aware not to replace one mine by another
		do
		{
			x = rand() % MAXX;
			y = rand() % MAXY;
		} while(pole[x][y] == -1);

		pole[x][y] = -1;
	}

	// And now generating the nums
	for(x = 0; x < MAXX; x++)
		for(y = 0; y < MAXY; y++)
		{
			if(pole[x][y] != -1)
			{
				// Num of mine around this cell
				int min = 0; // by default
	
				// right
				if(x < MAXX - 1) if(pole[x+1][y] == -1) min++;
	
				// left
				if(x > 0) if(pole[x-1][y] == -1) min++;
	
				// up
				if(y > 0) if(pole[x][y-1] == -1) min++;

				// down
				if(y <  MAXY - 1) if(pole[x][y+1] == -1) min++;
				
				// left up
				if(x > 0 && y > 0) if(pole[x-1][y-1] == -1) min++;

				// left down
				if(x > 0 && y < MAXY - 1) if(pole[x-1][y+1] == -1) min++;

				// right up
				if(x < MAXX -1 && y > 0) if(pole[x+1][y-1] == -1) min++;

				// right down
				if(x < MAXX - 1 && y < MAXY - 1) if(pole[x+1][y+1] == -1) min++;

				pole[x][y] = min;
			}
		}
} // end of mines_generate_field()

/* Action for creating new game */
void mines_new_game()
{
	// Generate mine fild
	mines_generate_field();

	// Iner counter of revealed mines
	mines = 0;

	// Print fild
	attron(COLOR_PAIR(1));
	int x, y;
	for(x = 0; x < MAXX; x++)
	{
		for(y = 0; y < MAXY; y++)
		{
			move(POSY(resy, MAXY, y), POSX(resx, MAXX, x));
			printw("0 ");
		}
	}

	// Jump to the zero zero 
	move(MPOSY(0), MPOSX(0));
}

/* will show all mines */
int mines_show_all_mines()
{
	int x, y;
	attron(COLOR_PAIR(2));
	for(x = 0; x < MAXX; x++)
		for(y = 0; y < MAXY; y++)
			if(pole[x][y] == -1)
				mvprintw(MPOSY(y), MPOSX(x), "x");
	attron(COLOR_PAIR(1));
}

/* Actions for end game */
int mines_end_game(int success)
{
	// Show mines
	mines_show_all_mines();

	// Info text:
	if(success)	attron(COLOR_PAIR(2));
	else		attron(COLOR_PAIR(10));

	char *msg;
	if(success)  	msg = "THE END - Play new game? [y/n]";
	else		msg = "YOU WIN - Play new game? [y/n]";

	mvprintw(resy - 1, resx / 2 - strlen(msg) / 2, "%s", msg);

	int d = getch();

	while(d != 'y' && d != 'n' && d != KEY_SPACE && d != KEY_ENTER)
		d = getch();

	if(d == 'n')
	{
		return true;
	}

	mines_new_game();
	attron(COLOR_PAIR(9));

	for(d = 0; d < resx; d++)
		mvprintw(resy - 1, d, " ");

	return false;
}

/* Debug function for print fild */
void print_field()
{
	int x, y;
	for(x = 0; x < MAXX; x ++)
	{
		for(y = 0; y < MAXY; y++)
		{
			printf("\t%d", pole[x][y]);
		}
		printf("\n");
	}
}

/* Mark given position visible */
bool mines_mark(int x, int y)
{
	move(MPOSY(y), MPOSX(x));

	switch(pole[x][y])
	{
		case -2:
			break;
		case 0:
			// Blank area
			printw(" ");

			// infinity loop protection
			pole[x][y] = -2;

			// right
			if(x < MAXX - 1) mines_mark(x+1, y);

			// left
			if(x > 0) mines_mark(x-1, y);

			// up
			if(y > 0) mines_mark(x, y-1);

			// down
			if(y <  MAXY - 1) mines_mark(x, y+1);
			
			// left up
			if(x > 0 && y > 0) mines_mark(x-1, y-1);

			// left down
			if(x > 0 && y < MAXY - 1) mines_mark(x-1, y+1);

			// right up
			if(x < MAXX -1 && y > 0) mines_mark(x+1, y-1);

			// right down
			if(x < MAXX - 1 && y < MAXY - 1) mines_mark(x+1,y+1);
			
			break;
		case -1:
			// Ups
			mines_show_all_mines();
			// User finds a mine...
			return false;
		case 1:
			attron(COLOR_PAIR(3));
			printw("%d", pole[x][y]);
			break;
		case 2:
			attron(COLOR_PAIR(5));
			printw("%d", pole[x][y]);
			break;
		case 3:
			attron(COLOR_PAIR(6));
			printw("%d", pole[x][y]);
			break;
		case 4:
			attron(COLOR_PAIR(7));
			printw("%d", pole[x][y]);
			break;
		default:
			attron(COLOR_PAIR(8));
			printw("%d", pole[x][y]);
			break;
	}
	attron(COLOR_PAIR(1));

	// Everything is O.K.
	return true;
}
/* Prints help for this programme */
void mines_print_help()
{
	printf( "Miny - ncurses version of Microsoft game 'mines'\n"
		"\tBy Jarcec <jarcec@jarcec.net>\n"
		"\n"
		"In game controls\n"
		"arrows - movement in the field\n"
		"space - mark cell as a mine\n"
		"enter - unhide cell\n"
		"escape - end game (on some terminals, pres twice)\n"
		"\n"
		"Command line options:\n"
		"-h --help\t\tShow this help\n"
		"-m --mines [int]\tSet num of the mines in field\n"
		"-x --width [int]\tSet width of field (axis x)\n"
		"-y --height [int]\tSet height of field (axis y)\n"
		"\n"	
		);
}

/* End program - eg, release used memory and exit ncurses mode */
void mines_exit()
{
	int x;
	for(x = 0; x < MAXX; x++)
	{
		free(pole[x]);
		free(mask[x]);
	}
	free(pole);
	free(mask);

	endwin();
}

/* Main function of programme */
int main(int argc, char **argv)
{
	// Variables for main function
	int c, temp;
	int ax = 0, ay = 0;

	// Hack no1 - getting parametrs
	char getopt_short[] = "hm:x:y:";
	struct option getopt_options[] = {
						{ "help", 0, NULL, 'h' },
						{ "mines", 1, NULL, 'm' },
						{ "width", 1, NULL, 'x' },
						{ "height", 1, NULL, 'y' }
					};

	while((c = getopt_long(argc, argv, getopt_short, getopt_options, NULL)) != -1)
	{
		switch(c)
		{
			case 'h':
				mines_print_help();
				return 0;
			case 'm':
				sscanf(optarg, "%d", &temp);
				mines_count = temp;
				break;
			case 'x':
				sscanf(optarg, "%d", &temp);
				MAXX = temp;
				break;
			case 'y':
				sscanf(optarg, "%d", &temp);
				MAXY = temp;
				break;
			case '?':
				printf("Unknown option, try -h for help.\n");
				return 0;
		}
	}
	
	// Have we possible settings of field?
	if((MAXX * MAXY * mines_count) == 0)
	{
		// if one of these three variables is set to zero
		printf("Width, height or count of mines is set to zero! For help add -h to options.\n");
		return;
	}

	if(MAXX * MAXY < mines_count)
	{
		// More mines that can be stored in field
		printf("You want to have more mines then can be store in specified field. Please change the size of field or lower the count of mines. For help add -h to options.\n");
		return 0;
	}
		
	// nastaveni nahodnosti
	srand(time(NULL));

	// Init ncurses
	initscr();
	start_color();
	keypad(stdscr, true);
	noecho();
	init_pair(1, COLOR_BLACK, COLOR_WHITE);
	init_pair(2, COLOR_BLACK, COLOR_RED);
	init_pair(4, COLOR_BLACK, COLOR_YELLOW);
	init_pair(3, COLOR_BLUE, COLOR_WHITE);
	init_pair(5, COLOR_GREEN, COLOR_WHITE);
	init_pair(6, COLOR_RED, COLOR_WHITE);
	init_pair(7, COLOR_MAGENTA, COLOR_WHITE);
	init_pair(8, COLOR_CYAN, COLOR_WHITE);
	init_pair(9, COLOR_BLACK, COLOR_BLACK);
	init_pair(10, COLOR_BLACK, COLOR_GREEN);

	// Initializaton of array for holding field and mask of that field
	pole = (char **)malloc(sizeof(char *) * MAXX);
	mask = (char **)malloc(sizeof(char *) * MAXX);
	for(ax = 0; ax < MAXX; ax++)
	{
		pole[ax] = (char *)malloc(sizeof(char) * MAXY);
		mask[ax] = (char *)malloc(sizeof(char) * MAXY);
	}

	// Get properties
	getmaxyx(stdscr, resy, resx);

	// Print title
	char *msg = "Mines";
	mvprintw(0, resx / 2 - strlen(msg) / 2 , "%s", msg);

	// generate new game
	mines_new_game();

	// For storing actual position (cursor)
	ax = 0; ay = 0;
	
	// Loop of programme
	c = 'n';
	while(c != KEY_ESCAPE)
	{
		refresh();
		c = getch();
		switch(c)
		{
			/* Pohyby klaves */
			case KEY_RIGHT:
				if(ax < MAXX - 1)
				{
					ax++;
				}
				break;
			case KEY_LEFT:
				if(ax > 0)
				{
					ax--;
				}
				break;
			case KEY_UP:
				if(ay > 0)
				{
					ay--;
				}
				break;
			case KEY_DOWN:
				if(ay < MAXY - 1)
				{
					ay++;
				}
				break;
			/* Moznosti s polem */
			case KEY_ENTER:
				// in case, the fild is *not* marked
				if(!mask[ax][ay])
					if(!mines_mark(ax, ay)) // User click on mine (bad action)
					{
						if(mines_end_game(true))
						{
							//endwin();
							mines_exit();
							return 0;
						}
					}
				break;
			case KEY_SPACE:
			
				if(mask[ax][ay])
				{
					attron(COLOR_PAIR(1));
					printw("0");

					// Recording marking
					mask[ax][ay] = 0;
					mines--;
				} else {
					attron(COLOR_PAIR(4));
					printw("x");
					attron(COLOR_PAIR(1));

					// Recording marking
					mask[ax][ay] = 1;
					mines++;

					// Now checkign whether this is end game
					if(mines == mines_count)
					{
						
						int x, y, end = 0;
						for(x = 0; x < MAXX; x++)
							for(y = 0; y < MAXY; y++)
								if(mask[x][y] && pole[x][y] != -1)
								{
									end = 1;
									break;
								}

						if(mines_end_game(end))
						{
							//endwin();
							mines_exit();
							return 0;
						}
					}
				}
				break;
		}
		move(MPOSY(ay), MPOSX(ax));
	}

	// C'est tout
//	endwin();
	mines_exit();
//	print_field(); // debug
	return 0;
}
