/*
 * main.c
 * 
 * Copyright (C) 2006  Josef Kufner <jk@myserver.cz>
 * 
 * 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.
 *
 */

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>

#include "message.h"

#define DEBUG 10
#include "debug.h"

#define FIELD_SIZE	24
#define FIELD_COUNT	15

#define send(arg...) {							\
	MESSAGE *msg;							\
	msg = message_create(0, 0);					\
	message_append(msg, MESSAGE_PART_DATA, ##arg, MESSAGE_END);	\
	message_to_file(msg, stdout, 1);				\
	fprintf(stderr, "<- "); message_to_file(msg, stderr, 1);	\
	message_destroy(msg);						\
}


static void initialize_board(void);
static int add_piece(int x, int y, int player);
static void process_message (MESSAGE *msg);
	
enum {
	S_INIT, S_PLAY_ME, S_PLAY_YOU, S_PLAY_OBSERVER
};
enum {
	PLAYER_NONE, PLAYER_O, PLAYER_X, PLAYER__MAX
};

static int board[FIELD_COUNT][FIELD_COUNT];
static int state = S_INIT, game_started = 0, play_state = S_PLAY_OBSERVER;
static char *player_addr[PLAYER__MAX];
static int my_role = PLAYER_NONE;
static int last_turn = PLAYER_NONE;

static void show_game_over(const char *text)
{
	assert(text != NULL);

	send(MESSAGE_STRING, "Ui",
			MESSAGE_STRING, "Hra skončila",
			MESSAGE_STRING, text);
}

static void initialize_board(void)
{
	/* zero */
	memset(board, 0, sizeof(board));
	
	/* load board */
	send(MESSAGE_STRING, "Um", MESSAGE_STRING, "Piškvorky");
	send(MESSAGE_STRING, "Br");
	send(MESSAGE_STRING, "Bil", MESSAGE_STRING, "boards/piskvorky.png", MESSAGE_INTEGER, 0);
	send(MESSAGE_STRING, "Boa", MESSAGE_INTEGER, 0, MESSAGE_INTEGER, 0, MESSAGE_INTEGER, 0,
			MESSAGE_INTEGER, 0, MESSAGE_INTEGER, -1, MESSAGE_INTEGER, -1);
	send(MESSAGE_STRING, "Bu");

	/* load pieces */
	send(MESSAGE_STRING, "Bil", MESSAGE_STRING, "cursors/piskvorky-highlight.png", MESSAGE_INTEGER, PLAYER__MAX);
	send(MESSAGE_STRING, "Bil", MESSAGE_STRING, "pieces/piskvorky-circle.png", MESSAGE_INTEGER, PLAYER_O);
	send(MESSAGE_STRING, "Bil", MESSAGE_STRING, "pieces/piskvorky-cross.png", MESSAGE_INTEGER, PLAYER_X);
	
	send(MESSAGE_STRING, "Boa", MESSAGE_INTEGER, 1, MESSAGE_INTEGER, PLAYER__MAX,
			MESSAGE_INTEGER, FIELD_COUNT / 2 * FIELD_SIZE,
			MESSAGE_INTEGER, FIELD_COUNT / 2 * FIELD_SIZE,
			MESSAGE_INTEGER, -1, MESSAGE_INTEGER, -1);

	send(MESSAGE_STRING, "Sgr", MESSAGE_STRING, "X: křížek", MESSAGE_STRING, "O: kolečko");

	/* ready */
	send(MESSAGE_STRING, "Sgl");

	return;
}


static int add_piece(int x, int y, int player)
{
	assert(player == PLAYER_O || player == PLAYER_X);
	assert(x >= 0);
	assert(x < FIELD_COUNT);
	assert(y >= 0);
	assert(y < FIELD_COUNT);

	if (board[x][y]) {
		return 0;
	} else {
		board[x][y] = player;
		send(MESSAGE_STRING, "Boa",
				MESSAGE_INTEGER, x + y * FIELD_COUNT + 50,
				MESSAGE_INTEGER, player,
				MESSAGE_INTEGER, x * FIELD_SIZE,
				MESSAGE_INTEGER, y * FIELD_SIZE,
				MESSAGE_INTEGER, -1,
				MESSAGE_INTEGER, -1);
		send(MESSAGE_STRING, "Bom", MESSAGE_INTEGER, 1,
				MESSAGE_INTEGER, x * FIELD_SIZE,
				MESSAGE_INTEGER, y * FIELD_SIZE);
		send(MESSAGE_STRING, "Bu");
		return 1;
	}
}


static int check_for_victory(int last_x, int last_y)
{
	const int dir[4][2] = {
		/* N	*/ {  0, -1 },
		/* N-E	*/ {  1, -1 },
		/* E	*/ {  1,  0 },
		/* S-E	*/ {  1,  1 }
	};
	int count[4] = { 1, 1, 1, 1};
	int d, i, s, x, y, n = 0, ret = 0;

	for (s = -1; s <= 1; s += 2) {				/* dve poloviny */
		for (d = 0; d < 4; d++) {			/* 4 smery */
			for (i = 1; i < 5; i++) {		/* 5 policek bez stredu */
				x = last_x + i * s * dir[d][0];
				y = last_y + i * s * dir[d][1];
				
				if (x < 0 || x >= FIELD_COUNT || y < 0 || y >= FIELD_COUNT)
					break;
				
				if (board[x][y] == board[last_x][last_y])
					count[d]++;
				else
					break;
			}
		}
	}

	/* pokud bylo napocitano 5 znacek, zvyrazni viteznou sadu */
	for (d = 0; d < 4; d++) {
		if (count[d] >= 5) {
			for (s = -1; s <= 1; s += 2) {				/* dve poloviny */
				for (i = 0; i < 5; i++) {			/* 5 policek na kazdou stranu */
					x = last_x + i * s * dir[d][0];
					y = last_y + i * s * dir[d][1];
					
					if (x < 0 || x >= FIELD_COUNT || y < 0 || y >= FIELD_COUNT)
						break;
					
					if (board[x][y] == board[last_x][last_y]) {
						send(MESSAGE_STRING, "Boa",
								MESSAGE_INTEGER, ++n,
								MESSAGE_INTEGER, PLAYER__MAX,
								MESSAGE_INTEGER, x * FIELD_SIZE,
								MESSAGE_INTEGER, y * FIELD_SIZE,
								MESSAGE_INTEGER, -1,
								MESSAGE_INTEGER, -1);
					} else {
						break;
					}
				}
			}
			send(MESSAGE_STRING, "Bu");
			ret = 1;
		}
	}
	return ret;
}


static void process_message (MESSAGE *msg)
{
	char *cmd, *addr, *nick, *role, *cat, *mod, *opt, *val, *desc;
	int x, y, is_remote, turn_role;
	
	if (message_get(msg, MESSAGE_PART_DATA, MESSAGE_STRING, &cmd, MESSAGE_END)) {
		switch (cmd[0]) {
			case 'G': /* Game */
				switch (cmd[1]) {
					case 'c': /* game check request */
						if (player_addr[PLAYER_X])
							free(player_addr[PLAYER_X]);
						player_addr[PLAYER_X] = NULL;
						if (player_addr[PLAYER_O])
							free(player_addr[PLAYER_O]);
						player_addr[PLAYER_O] = NULL;
						
						send(MESSAGE_STRING, "Pl");
						break;

					case 's': /* game start */
						game_started = 1;
						state = play_state;
						send(MESSAGE_STRING, "Us", MESSAGE_INTEGER, my_role == PLAYER_X ? 2 : 1);
						break;

					case 'o': /* game over */
						send(MESSAGE_STRING, "Us", MESSAGE_INTEGER, 0);
						exit(0);
						break;

					case 'w': /* world request */
						{
							char str[FIELD_COUNT * FIELD_COUNT + 1];
							char on_turn[2] = { 0, 0 };
							int r, c;
							char symbol[4] = { '.', 'o', 'x', 0 };
							
							DBG_MSG(1, "Sending world ...");

							on_turn[0] = symbol[last_turn];
							str[FIELD_COUNT * FIELD_COUNT] = 0;
							for (r = 0; r < FIELD_COUNT; r++) {
								for (c = 0; c < FIELD_COUNT; c++) {
									str[r * FIELD_COUNT + c] = symbol[board[r][c]];
								}
							}

							send(MESSAGE_STRING, "Sw", MESSAGE_STRING, "Hra je takovato...",
									MESSAGE_STRING, on_turn, MESSAGE_STRING, str);
						}
				}
				break;

			case 'S': /* Session */
				switch (cmd[1]) {
					case 't': /* turn */
						if (game_started && message_get(msg, MESSAGE_PART_DATA, MESSAGE_SKIP, MESSAGE_SKIP,
									MESSAGE_INTEGER, &x, MESSAGE_INTEGER, &y,
									MESSAGE_INTEGER, &turn_role, MESSAGE_END)
								&& message_get(msg, MESSAGE_PART_SENDER_INFO,
									MESSAGE_SKIP, MESSAGE_INTEGER, &is_remote, MESSAGE_END))
						{	
							last_turn = turn_role;
							add_piece(x, y, turn_role == PLAYER_X ? PLAYER_X : PLAYER_O);

							if (check_for_victory(x, y)) {
								if (state == S_PLAY_OBSERVER) {
									show_game_over(turn_role == PLAYER_X ? "Vyhrál křížek.":"Vyhrálo kolečko.");
								} else {
									if (is_remote) {
										show_game_over("Prohrál jsi.");
									} else {
										show_game_over("Vyhrál jsi.");
									}
									send(MESSAGE_STRING, "Us", MESSAGE_INTEGER, 0);
								}
								send(MESSAGE_STRING, "Sgo");
								exit(0);
							} else {
								if (state != S_PLAY_OBSERVER) {
									state = S_PLAY_ME;
									send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, 2);
								}
							}
						}
						break;
						
					case 'u': /* surrender */
						{
							const char *address;
							int is_remote;

							if(message_get(msg, MESSAGE_PART_SENDER_INFO,
										MESSAGE_STRING, &address,
										MESSAGE_INTEGER, &is_remote,
										MESSAGE_END))
							{
								if (!player_addr[my_role] || !address) {
									show_game_over("Hra byla neočekávaně ukončena.");
								} else if (strcmp(player_addr[my_role], address) == 0) {
									show_game_over("Vzdal jsi se.");
								} else {
									show_game_over("Vyhrál jsi, soupeř se vzdal.");
								}
								send(MESSAGE_STRING, "Sgo");
								exit(0);
							}
						}
						break;

					case 'w': /* world received */
						{
							char *str;
							char *on_turn;
							int r, c;

							//on_turn[0] = symbol[last_turn];


							if (message_get(msg, MESSAGE_PART_DATA, MESSAGE_SKIP, MESSAGE_SKIP,
										MESSAGE_STRING, &on_turn, MESSAGE_STRING, &str, MESSAGE_END)
									&& message_get(msg, MESSAGE_PART_SENDER_INFO,
										MESSAGE_SKIP, MESSAGE_INTEGER, &is_remote, MESSAGE_END))
							{
								if (strlen(str) < FIELD_COUNT * FIELD_COUNT || !is_remote)
									break;

								DBG_MSG(1, "World received...");
								
								for (r = 0; r < FIELD_COUNT; r++) {
									for (c = 0; c < FIELD_COUNT; c++) {
										board[r][c] = PLAYER_NONE;
										switch (str[r * FIELD_COUNT + c]) {
											case 'x':
												add_piece(r, c, PLAYER_X);
												break;
											case 'o':
												add_piece(r, c, PLAYER_O);
												break;
										}
									}
								}
							}
						}
				}
				break;

			case 'B': /* Board */
				if (state != S_PLAY_ME || !game_started)
					break;

				switch (cmd[1]) {
					case 'b': /* mouse button */
						switch (cmd[2]) {
							case 'p': /* pressed */
								if (message_get(msg, MESSAGE_PART_DATA, MESSAGE_SKIP, MESSAGE_SKIP, MESSAGE_SKIP,
										MESSAGE_INTEGER, &x, MESSAGE_INTEGER, &y, MESSAGE_END)
										&& state == S_PLAY_ME)
								{
									x /= FIELD_SIZE;
									y /= FIELD_SIZE;
									if (add_piece(x, y, my_role)) {
										desc = g_strdup_printf("%s na sloupec %d, řádek %d",
												my_role == PLAYER_X ? "křížek":"kolečko", x, y);
										send(MESSAGE_STRING, "St", MESSAGE_STRING, desc, MESSAGE_INTEGER, x, MESSAGE_INTEGER, y,
												MESSAGE_INTEGER, my_role);
										g_free(desc);

										state = S_PLAY_YOU;
										send(MESSAGE_STRING, "Us", MESSAGE_INTEGER, 1);
									}
								}
								break;
						}	
						break;
				}
				break;

			case 'P': /* Player list */
				switch (cmd[1]) {
					case 'i':
						DBG_MSG(2, "Player info:");
						message_get(msg, MESSAGE_PART_DATA, MESSAGE_SKIP, MESSAGE_STRING, &addr,
								MESSAGE_STRING, &nick, MESSAGE_STRING, &role, MESSAGE_END);
						if (role[0] == 'X') {
							if (player_addr[PLAYER_X])
								free(player_addr[PLAYER_X]);
							player_addr[PLAYER_X] = strdup(addr);
						} else if (role[0] == 'O') {
							if (player_addr[PLAYER_O])
								free(player_addr[PLAYER_O]);
							player_addr[PLAYER_O] = strdup(addr);
						}
						break;
						
					case 'l':
						switch (cmd[2]) {
							case 'b':
								DBG_MSG(2, "Receiving players info...");
								break;

							case 'e':
								DBG_MSG(2, "End of players info.");
								if (player_addr[PLAYER_O] && player_addr[PLAYER_X]) {
									send(MESSAGE_STRING, "Og",
											MESSAGE_STRING, "main",
											MESSAGE_STRING, "game",
											MESSAGE_STRING, "my_role");
								} else {
									send(MESSAGE_STRING, "Sgd");
								}
								break;
						}
						break;
				}
				break;

			case 'O': /* Option */
				switch (cmd[1]) {
					case 'r':
						if (message_get(msg,MESSAGE_PART_DATA, MESSAGE_SKIP, MESSAGE_STRING, &cat, MESSAGE_STRING, &mod,
									MESSAGE_STRING, &opt, MESSAGE_STRING, &val, MESSAGE_END)) {
							DBG_MSG(2, "Received option value: [ %s :: %s :: %s ] = \"%s\"", cat, mod, opt, val);
							if (strcmp(cat, "main") == 0 && strcmp(mod, "game") == 0 && strcmp(opt, "my_role") == 0) {
								DBG_MSG(3, "My role is %s", val);
								if (val[0] == 'X') {
									my_role = PLAYER_X;
									play_state = S_PLAY_ME;
								} else if (val[0] == 'O') {
									my_role = PLAYER_O;
									play_state = S_PLAY_YOU;
								} else {
									play_state = S_PLAY_OBSERVER;
								}

#if 0
								if (my_role == PLAYER_O) {
									send(MESSAGE_STRING, "Sgd");
								} else {
									send(MESSAGE_STRING, "Sga");
								}
#else
								send(MESSAGE_STRING, "Sga");
#endif
							}
						}
				}
				
			case 'T': /* test/debug */
				switch (cmd[1]) {
					case 'p': /* ping */
						DBG_MSG(3, "Ping.");
						send(MESSAGE_STRING, "Tr");
						break;
				}
		}
	}
	return;
}


int main (void)
{
	MESSAGE *msg = NULL;
	int i, rd = 1;
	char buf[256];
	
	DBG_MSG(1, "Initializing...");
	memset(player_addr, 0, sizeof(player_addr));
	initialize_board();

	//while ((ch = fgetc(stdin)) != EOF) {
	while (rd > 0 || (rd == -1 && errno == EINTR)) {
		rd = read(0, buf, sizeof(buf));
		for (i = 0; i < rd; i++) {
			if (message_from_stream(0, 0, buf[i], &msg) == 1) {
				DBG_MSG(1, "Received message...");
#if DEBUG > 2
				fprintf(stderr, "-> "); message_to_file(msg, stderr, 1);
#endif
				process_message(msg);
				message_destroy(msg);
				msg = NULL;
				DBG_MSG(1, "Message processed.");
			}
		}
	}
	
	return 0;
}

/* vim:set encoding=utf8: */
