/*
 * 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 "message.h"

#define DEBUG 5
#include "debug.h"

#define FIELD_SIZE	24
#define FIELD_COUNT	15

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


static void player_clear(void);
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_CHECK, S_PREPARE, 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;
static char *player_addr[PLAYER__MAX];
static int my_role = PLAYER_NONE;


static void player_clear(void)
{
	int i;
	
	for (i = 0; i < PLAYER__MAX; i++) {
		if (player_addr[i])
			free(player_addr[i]);
		player_addr[i] = NULL;
	}
}

static void initialize_board(void)
{
	/* zero */
	memset(board, 0, sizeof(board));
	
	/* load board */
	send(MESSAGE_STRING, "Bst", 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, 0,
			MESSAGE_INTEGER, 0, MESSAGE_INTEGER, -1, MESSAGE_INTEGER, -1);

	send(MESSAGE_STRING, "Sr", 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, *opt, *val, *desc;
	int x, y, is_remote, turn_role;
	
	if (message_get(msg, MESSAGE_STRING, &cmd, MESSAGE_END)) {
		switch (cmd[0]) {
			case 'S': /* Session */
				switch (cmd[1]) {
					case 'g': /* game */
						switch (cmd[2]) {
							case 'c': /* game check request */
								if (state == S_INIT) {
									state = S_CHECK;
									send(MESSAGE_STRING, "Pl");
								}
								break;

							case 's': /* start */
								game_started = 1;
								send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, my_role == PLAYER_X ? 2 : 1);
								break;
						}
						break;

					case 'p': /* player */
						switch (cmd[2]) {
							case 't': /* turn */
								if (game_started && message_get(msg, MESSAGE_SKIP, MESSAGE_SKIP,
											MESSAGE_INTEGER, &x, MESSAGE_INTEGER, &y,
											MESSAGE_INTEGER, &turn_role,
											MESSAGE_SKIP, MESSAGE_INTEGER, &is_remote, MESSAGE_END)
										&& (state == S_PLAY_YOU || state == S_PLAY_OBSERVER) && is_remote)
								{	
									if (state != S_PLAY_OBSERVER)
										add_piece(x, y, my_role == PLAYER_X ? PLAYER_O : PLAYER_X);
									else
										add_piece(x, y, turn_role == PLAYER_X ? PLAYER_X : PLAYER_O);

									if (check_for_victory(x, y)) {
										if (state == S_PLAY_OBSERVER) {
											send(MESSAGE_STRING, "Bst", MESSAGE_STRING, 
													my_role == PLAYER_X ? "Vyhrál křížek.":"Vyhrálo kolečko.");
										} else {
											send(MESSAGE_STRING, "Bst", MESSAGE_STRING, "Prohrál jsi.");
											send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, 0);
										}
										exit(0);
									} else {
										if (state != S_PLAY_OBSERVER) {
											state = S_PLAY_ME;
											send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, 2);
										}
									}
								}
								break;
						}
						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_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, "Spt", MESSAGE_STRING, desc, MESSAGE_INTEGER, x, MESSAGE_INTEGER, y,
												MESSAGE_INTEGER, my_role);
										g_free(desc);
										
										if (check_for_victory(x, y)) {
											send(MESSAGE_STRING, "Bst", MESSAGE_STRING, "Vyhrál jsi.");
											send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, 0);
											exit(0);
										} else {
											state = S_PLAY_YOU;
											send(MESSAGE_STRING, "Bss", MESSAGE_INTEGER, 1);
										}
									}
								}
								break;
						}	
						break;
				}
				break;

			case 'P': /* Player list */
				if (state != S_CHECK)
					break;
				
				switch (cmd[1]) {
					case 'i':
						DBG_MSG(2, "Player info:");
						message_get(msg, MESSAGE_SKIP, MESSAGE_STRING, &addr, MESSAGE_STRING, &nick, MESSAGE_STRING, &role, MESSAGE_END);
						if ((role[0] == 'X' && player_addr[PLAYER_X])
							|| (role[0] == 'O' && player_addr[PLAYER_O]))
						{
							player_clear();
							send(MESSAGE_STRING, "Sgr");
							state = S_INIT;
						} else {
							if (role[0] == 'X')
								player_addr[PLAYER_X] = strdup(addr);
							else if (role[0] == '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]) {
									state = S_PREPARE;
									send(MESSAGE_STRING, "Og", MESSAGE_STRING, "main", MESSAGE_STRING, "game.my_role");
								} else {
									state = S_INIT;
									send(MESSAGE_STRING, "Sgr");
								}
								break;
						}
						break;
				}
				break;

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


int main (void)
{
	MESSAGE *msg = NULL;
	int msg_state = 0, ch;
	GString *msg_str = NULL;
	
	DBG_MSG(1, "Initializing...");
	memset(player_addr, 0, sizeof(player_addr));
	initialize_board();

	while ((ch = fgetc(stdin)) != EOF) {
		if (message_from_stream(0, 0, ch, &msg_state, &msg_str, &msg) == 1) {
			DBG_MSG(1, "Received message...");
			fprintf(stderr, "-> "); message_to_file(msg, stderr, 1);
			process_message(msg);
			message_destroy(msg);
			msg = NULL;
			DBG_MSG(1, "Message processed.");
		}
	}
	
	return 0;
}

/* vim:set encoding=utf8: */
