/* 
 * Book Share Process Layer
 * 
 * process.c
 *
 * Interface to the book share system layer implemented at the client side.
 * 
 * Processes an command line, according to each type.
 * 
 */

#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <bs_api.h>
#include <servers.h>
#include "process.h"
#include "request_list.h"
#include "str_list.h"


/*
 * Internal private global variables
 */
 

// neighbour search threads
static pthread_t neighbour_threads[MAX_TITLES_SIZE][MAX_NR_SERVERS];
static int neighbour_thread_status[MAX_TITLES_SIZE][MAX_NR_SERVERS];
static pthread_mutex_t Neighbours_mutex[MAX_TITLES_SIZE];
int neighbours_nr[MAX_TITLES_SIZE];

// book search threads
static pthread_t book_threads[MAX_TITLES_SIZE];
static int book_thread_status[MAX_TITLES_SIZE];
static pthread_mutex_t Book_mutex;
int books_nr = 0;

// book take threads
static pthread_t Take_threads[MAX_TITLES_SIZE];
static int Take_thread_status[MAX_TITLES_SIZE];
static pthread_mutex_t Take_mutex;
int Take_nr = 0;
	
str_list_t* visited_servers[MAX_TITLES_SIZE];

static pthread_mutex_t Write_cover;

// switch
static int V_flag;

// reserves confirmed
static int Reserves_confirmed;

// username
static char Username[MAX_USERNAME_SIZE];

// password
static char Password[MAX_PASSWORD_SIZE];

// predefined counter address
static char Pc_address[MAX_ADDRESS_SIZE];

// predefined counter port
static int Pc_port;

// predefined counter id
static char Pc_id[MAX_COUNTER_ID_SIZE];

static void* process_another_search(void *arguments);
static void* neighbour_thread_search(void *arg);
static void* take_thread(void *arg);

static int neighbour_threads_finished();
static int take_threads_finished();
static int book_threads_finished();


void process_init(char *username, char *password, int v_flag) {
	V_flag = v_flag;
	servers_get_default(Pc_id);
	servers_get(Pc_id, Pc_address, &Pc_port);
	servers_new(SERVER_LIST, V_flag);
	strncpy(Username, username, MAX_USERNAME_SIZE);
	strncpy(Password, password, MAX_PASSWORD_SIZE);
	bs_init(V_flag);
}


void process_give(char *command) {
	printf("\33[1m[process_client]\33[0m give\n");
	
	bs_give(Pc_address, Pc_port, command + 5);
}


static void *process_another_search(void* arguments) {
	char neighbour[MAX_NEIGHBOUR_SIZE];
	thread_search_arg_t thread_arguments[MAX_NR_SERVERS];
	char address[MAX_NR_SERVERS][MAX_ADDRESS_SIZE];
	
	int index;
	for (index = 0; index < MAX_TITLES_SIZE; ++index) {
		if (book_threads[index] == pthread_self()) {
			break;
		}
	}
	
	system_res_search_t* arg = (system_res_search_t*) arguments;
	
	neighbours_nr[index] = 0;
	memset(&neighbour_thread_status[index], 0, MAX_NR_SERVERS);
	
	// inserts predefined counter
	char predefined_server[MAX_NEIGHBOUR_SIZE];
	sprintf(predefined_server, "%s:%d", Pc_address, Pc_port);
	str_lst_insert(visited_servers[index], predefined_server);

	// launches a thread for each neighbour
	for (int i = 0, neighbour_len; i < strlen(arg->neighbours);	i += neighbour_len + 1, ++neighbours_nr[index]) {
		int port, weight, j;
		
		// gets neighbour
		sscanf(arg->neighbours + i, "%s ", neighbour);
		neighbour_len = strlen(neighbour);
		
		// gets address, port, weight from neighbour
		for (j = 0; neighbour[j] != ':'; ++j) {
			address[neighbours_nr[index]][j] = neighbour[j];
		}
		address[neighbours_nr[index]][j] = '\0';
		sscanf(neighbour + j, ":%d/%d", &port, &weight);
		
		// inserts this neighbour into visited_servers
		sprintf(neighbour, "%s:%d", address[neighbours_nr[index]], port);
		str_lst_insert(visited_servers[index], neighbour);
		
		// creates a thread
		strncpy(thread_arguments[neighbours_nr[index]].booktitle, arg->title, MAX_BOOKNAME_SIZE);
		thread_arguments[neighbours_nr[index]].weight = weight + arg->weight;
		strncpy(thread_arguments[neighbours_nr[index]].address, address[neighbours_nr[index]], MAX_ADDRESS_SIZE);
		thread_arguments[neighbours_nr[index]].port = port;
		thread_arguments[neighbours_nr[index]].book_thread = index;
		pthread_create(&neighbour_threads[index][neighbours_nr[index]], NULL,
						neighbour_thread_search, &(thread_arguments[neighbours_nr[index]]));
	}
	
	// joins all threads and parses their responses
	sys_lst_iitem_t *returns[MAX_NR_SERVERS];
	int nr_books_found = 0;
	int found_where = -1;
	int books_found[neighbours_nr[index]];
	
	memset(&books_found, 0, neighbours_nr[index]);

	while (1) {
		pthread_mutex_lock(&Neighbours_mutex[index]);
		
		for (int j = 0; j < neighbours_nr[index]; ++j) {
			if (neighbour_thread_status[index][j] == 1) {
				pthread_join(neighbour_threads[index][j], (void*) &(returns[j]));
				
				if (strcmp(returns[j]->system_response.search.counter, "0") != 0) {
					books_found[nr_books_found] = j;
					++nr_books_found;
					found_where = j;
				}
				
				neighbour_thread_status[index][j] = 2;
			}
		}
		if (neighbour_threads_finished()) {
			pthread_mutex_unlock(&Neighbours_mutex[index]);
			break;
		}
		pthread_mutex_unlock(&Neighbours_mutex[index]);
	}
	
	str_list_t* new_neighbours_list = str_lst_new();
	
	if (nr_books_found == 1) {
		char counter_id[MAX_COUNTER_ID_SIZE];
		int l = 0;
		
		for (l = 0; l < MAX_COUNTER_ID_SIZE; ++l) {
			counter_id[l] = returns[found_where]->system_response.search.counter[l];
		}
		counter_id[l - 1] = '\0';
			
		printf("\33[1m'%s' reservado em %s, com Peso = %d.\33[0m\n", returns[found_where]->system_response.search.title,
			   counter_id, returns[found_where]->system_response.search.weight);
		
		pthread_mutex_lock(&Book_mutex);
		book_thread_status[index] = 1;
		pthread_mutex_unlock(&Book_mutex);
		
		str_lst_destroy(new_neighbours_list);
		
		for (int p = 0; p < neighbours_nr[index]; ++p) {
			free(returns[p]);
		}
		return NULL;
	}
	else if (nr_books_found == 0) {
		for (int j = 0; j < neighbours_nr[index]; ++j) {
			for (int k = 0, neighbour_len;
				k < strlen(returns[j]->system_response.search.neighbours);
				k += neighbour_len + 1) {
				
				char temp_neighbour[MAX_NEIGHBOUR_SIZE];
				char address[MAX_ADDRESS_SIZE];
				int port, weight, o;
				
				memset(&temp_neighbour, 0, MAX_NEIGHBOUR_SIZE);
				
				// gets neighbour
				sscanf(returns[j]->system_response.search.neighbours + k, "%s ", temp_neighbour);
				neighbour_len = strlen(temp_neighbour);
				
				// gets address, port, weight from neighbour
				for (o = 0; temp_neighbour[o] != ':'; ++o) {
					address[o] = temp_neighbour[o];
				}
				address[o] = '\0';
				sscanf(temp_neighbour + o, ":%d/%d", &port, &weight);
				
				weight += returns[j]->system_response.search.weight;
				
				char new_neighbour[MAX_NEIGHBOUR_SIZE];
				vprintf("Neighbour: %s, ", temp_neighbour);
				vprintf("Address: %s, ", address);
				vprintf("Port: %d, ", port);
				vprintf("Weight: %d\n", weight);
				
				sprintf(temp_neighbour, "%s:%d/%d", address, port, weight);
				sprintf(new_neighbour, "%s:%d", address, port);
				if (str_lst_find(visited_servers[index], new_neighbour) == NULL) {
					
					if (neighbour_find(new_neighbours_list, new_neighbour) == NULL) {
						str_lst_insert(new_neighbours_list, temp_neighbour);
					}
					else {
						str_lst_iitem_t* aux = neighbour_find(new_neighbours_list, new_neighbour);
						int aux_weight;
						
						// gets address, port
						for (j = 0; aux->value[j] != '/'; ++j);
						sscanf(aux->value + j, ":%d", &aux_weight);
						
						if (weight < aux_weight) {
							char *new_value = (char*) malloc((strlen(temp_neighbour) + 1) * sizeof(char));
							free(aux->value);
							aux->value = new_value;
						}
					}
				}
			}
		}
	}
	else if (nr_books_found > 1) {
		int less_weight = INT_MAX;
		char best_counter[MAX_COUNTER_ID_SIZE];

		for (int n = 0; n < nr_books_found; ++n) {			
			char id[MAX_COUNTER_ID_SIZE];
			char address[MAX_ADDRESS_SIZE];
			int port, weight = 0, o, p;
			
			// gets id
			for (p = 0; returns[books_found[n]]->system_response.search.counter[p] != ' '; ++p) {
				id[p] = returns[books_found[n]]->system_response.search.counter[p];
			}
			id[p] = '\0';
			
			for (o = p + 1; returns[books_found[n]]->system_response.search.counter[o] != ' '; ++o) {
				address[o - (p + 1)] = returns[books_found[n]]->system_response.search.counter[o];
			}
			address[o - p + 1] = '\0';
			sscanf(returns[books_found[n]]->system_response.search.counter + o, " %d", &port);
			
			weight = returns[books_found[n]]->system_response.search.weight;
			
			vprintf("Id: %s, Address: %s, Port: %d, Weight: %d\n", id, address, port, weight);
			
			//TODO: Peso igual -> ordem alfabética
			
			vprintf("less_weight: %d | weight: %d\n", less_weight, weight);
			if (less_weight > weight) {
				if (less_weight != INT_MAX) {
					int del_port;
					char del_address[MAX_ADDRESS_SIZE];
					servers_get(best_counter, del_address, &del_port);
					
					bs_cancel(del_address, del_port, Username, arg->title);
				}
				
				less_weight = weight;
				strncpy(best_counter, id, MAX_COUNTER_ID_SIZE);
				vprintf("weight: %d | id: %s\n", less_weight, best_counter);
			}
			else if (less_weight == weight) {
				vprintf("best_counter: %s | id: %s\n", best_counter, id);
				if (strcmp(best_counter, id) > 0) {
					int del_port;
					char del_address[MAX_ADDRESS_SIZE];
					servers_get(best_counter, del_address, &port);
					
					bs_cancel(del_address, del_port, Username, arg->title);
					
					strncpy(best_counter, id, MAX_COUNTER_ID_SIZE);
				}		
				
				bs_cancel(address, port, Username, arg->title);
			}
			else {
				bs_cancel(address, port, Username, arg->title);
			}
		
		}
		
		
		pthread_mutex_lock(&Book_mutex);
		book_thread_status[index] = 1;
		pthread_mutex_unlock(&Book_mutex);
		
		str_lst_destroy(new_neighbours_list);
		
		for (int p = 0; p < neighbours_nr[index]; ++p) {
			free(returns[p]);
		}
		
		printf("\33[1m'%s' reservado em %s, com Peso = %d.\33[0m\n",
			   arg->title, best_counter, less_weight);
				
		return NULL;
	}


	// received answers from all neighbours TODO: cancel TwatchMax
	// if new neighbours, recursive call
	char new_neighbours[MAX_NEIGHBOURS_SIZE] = NOSTR;
	char* new_neighbour;
		
	while ((new_neighbour = str_lst_remove(new_neighbours_list)) != NULL) {
		strcat(new_neighbours, new_neighbour);
		strcat(new_neighbours, " ");
		free(new_neighbour);
	}	
	
	if (strcmp(new_neighbours, NOSTR) != 0) {
		system_res_search_t *new_args = (system_res_search_t*) malloc(sizeof(system_res_search_t));
		strncpy(new_args->counter, arg->counter, MAX_COUNTER_ID_SIZE);
		strncpy(new_args->title, arg->title, MAX_BOOKNAME_SIZE);
		strncpy(new_args->neighbours, new_neighbours, MAX_NEIGHBOURS_SIZE);
		new_args->weight = arg->weight;
		
		process_another_search((void*) new_args);
		free(new_args);
	} else {
		printf("\33[1m'%s' indisponivel.\33[0m\n", arg->title);
	}
	
	for (int p = 0; p < neighbours_nr[index]; ++p) {
		free(returns[p]);
	}
	
	pthread_mutex_lock(&Book_mutex);
	book_thread_status[index] = 1;
	pthread_mutex_unlock(&Book_mutex);
	
	str_lst_destroy(new_neighbours_list);
	
	return NULL;
}


static int neighbour_threads_finished() {
	int finished = 1;
	
	int index;
	for (index = 0; index < MAX_TITLES_SIZE; ++index) {
		if (book_threads[index] == pthread_self()) {
			break;
		}
	}
		
	for (int i = 0; i < neighbours_nr[index]; ++i) {
		finished = finished && neighbour_thread_status[index][i];
	}
	
	return finished;
}


static int book_threads_finished() {
	int finished = 1;
	
	for (int i = 0; i < books_nr; ++i) {
		finished = finished && book_thread_status[i];
	}
	
	return finished;
}


static void* neighbour_thread_search(void* arg) {
	int i;
	sys_list_t* results;
	sys_lst_iitem_t* item;
	
	thread_search_arg_t* aux = (thread_search_arg_t*) arg;
	
	for (i = 0; i < MAX_NR_SERVERS; ++i) {
		if (neighbour_threads[aux->book_thread][i] == pthread_self()) {
			break;
		}
	}
	
	vprintf("------------ New neighbour_thread_search [%d]%d-------------\n", aux->book_thread, (int)pthread_self());
	
	if (bs_search(aux->address, aux->port, Username, Password, aux->booktitle, aux->weight, &results) == STAT_ERROR) {
		pthread_mutex_lock(&Neighbours_mutex[aux->book_thread]);
		neighbour_thread_status[aux->book_thread][i] = 1;
		pthread_mutex_unlock(&Neighbours_mutex[aux->book_thread]);
		
		//TODO
		return NULL;
	}
	item = sys_lst_pop(results);
	sys_lst_destroy(results);
	
	pthread_mutex_lock(&Neighbours_mutex[aux->book_thread]);
	neighbour_thread_status[aux->book_thread][i] = 1;
	pthread_mutex_unlock(&Neighbours_mutex[aux->book_thread]);
	
	vprintf("------------ neighbour_thread_search fim [%d]%d-------------\n", aux->book_thread, (int)pthread_self());
	
	vprintf("%s|%s|%d|%s|%s\n", item->system_response.search.title, item->system_response.search.counter,
		   item->system_response.search.weight, item->system_response.search.neighbours,
	  	   item->system_response.search.reason);
	return item;
}


void process_search(char *command) {
	printf("\33[1m[process_client]\33[0m search\n");
	int used_threads = 0;
	//TODO: Timer TsearchMax
	sys_lst_iitem_t* free_list[MAX_TITLES_SIZE];
	system_res_search_t *thread_arguments[MAX_TITLES_SIZE];
	sys_list_t* results;
	sys_lst_iitem_t* item;
	
	books_nr = 0;
	if (bs_search(Pc_address, Pc_port, Username, Password, command + 7, 0, &results) == STAT_ERROR) {
		//TODO
		return;
	}
	memset(&book_thread_status, 0, MAX_TITLES_SIZE);

	for (int i = 0; (item = sys_lst_pop(results)) != NULL; ++i) {
		thread_arguments[books_nr] = &(item->system_response.search);
		free_list[books_nr] = item;
		
		// authentication error
		if (item->status == ERROR) {
			printf("\33[1m%s\33[0m\n", item->system_response.search.reason);
			free(item);
			continue;
		}
		
		if (strcmp(thread_arguments[books_nr]->counter, "0") != 0) {
			// finds the book
			
			char counter_id[MAX_COUNTER_ID_SIZE];
			int l = 0;
			
			for (l = 0; l < MAX_COUNTER_ID_SIZE; ++l) {
				counter_id[l] = thread_arguments[books_nr]->counter[l];
			}
			counter_id[l - 1] = '\0';
			
			printf("\33[1m'%s' reservado em %s, com Peso = %d.\33[0m\n", thread_arguments[books_nr]->title,
				   counter_id, thread_arguments[books_nr]->weight);
			free(item);
		}
		else if (strlen(thread_arguments[books_nr]->neighbours) == 0) {
			// doesn't find the book but doesn't have any neighbours (1 server)
			printf("\33[1m'%s' indisponivel.\33[0m\n", thread_arguments[books_nr]->title);
			free(item);
		}
		else {
			// doesnt find the book and has neighbours
			used_threads = 1;
			// TODO: Timer TwatchMax
			visited_servers[books_nr] = str_lst_new();
			pthread_create(&book_threads[books_nr], NULL, process_another_search, thread_arguments[books_nr]);
			++books_nr;
		}
	}
	
	if (used_threads) {
		while (1) {
			pthread_mutex_lock(&Book_mutex);
			for (int j = 0; j < books_nr; ++j) {
				if (book_thread_status[j] == 1) {
					pthread_join(book_threads[j], NULL);
					book_thread_status[j] = 2;
				}
			}
			if (book_threads_finished()) {
				pthread_mutex_unlock(&Book_mutex);
				break;
			}
			pthread_mutex_unlock(&Book_mutex);
		}
		
		for (int i = 0; i < books_nr; ++i) {
			free(free_list[i]);
		}	
	}
	
	for (int j = 0; j < books_nr; ++j) {
		str_lst_destroy(visited_servers[j]);
	}
	sys_lst_destroy(results);
}


static void* take_thread(void *arg) {
	int i = 0;
	sys_list_t* results;
	thread_take_arg_t* arguments = (thread_take_arg_t*) arg;

	for (i = 0; i < Take_nr; ++i) {
		if (Take_threads[i] == pthread_self()) {
			break;
		}
	}
	
	if (bs_take(arguments->address, arguments->port, Username, arguments->titlecs, &results) == STAT_ERROR) {
		pthread_mutex_lock(&Take_mutex);
		Take_thread_status[i] = 1;
		pthread_mutex_unlock(&Take_mutex);
		
		Reserves_confirmed = 0;
		
		//TODO
		return NULL;
	}
	
	sys_lst_iitem_t *response;
	while ((response = sys_lst_pop(results)) != NULL) {
		int size = 0;
		
		if (response->status == ERROR) {
			printf("\33[1m%s\33[0m\n", response->system_response.take.reason);
			free(response);
			continue;
		}
		
		size = response->system_response.take.size;
		
		if (size == 0) {
			if (strlen(response->system_response.take.reason) != 0) {
				printf("\33[1m%s\33[0m\n", response->system_response.take.reason);
			}
			else {
				char counter_id[MAX_COUNTER_ID_SIZE];
				int l = 0;
				
				for (l = 0; l < MAX_COUNTER_ID_SIZE; ++l) {
					counter_id[l] = response->system_response.take.counter[l];
				}
				counter_id[l - 1] = '\0';
			
				printf("\33[1mReserva de '%s' em %s confirmada.\33[0m\n", response->system_response.take.title,
					  counter_id);
			}
			free(response);
			continue;
		}
		else {
			pthread_mutex_lock(&Write_cover);
			
			FILE *fp;
			char cover_filename[MAX_FILENAME_SIZE];
			
			strcpy(cover_filename, "./");
			strcpy(cover_filename + strlen(cover_filename), response->system_response.take.title);
			strcpy(cover_filename + strlen(cover_filename), COVER_EXTENSION);
			
			fp = fopen(cover_filename, "w+");
			
			if (fp == NULL) {
				exit(-1);
			}
			
			fwrite(response->system_response.take.cover, sizeof(char), size, fp);
			fclose(fp);
			
			char counter_id[MAX_COUNTER_ID_SIZE];
			int l = 0;
			
			for (l = 0; l < MAX_COUNTER_ID_SIZE; ++l) {
				counter_id[l] = response->system_response.take.counter[l];
			}
			counter_id[l - 1] = '\0';
			
			printf("\33[1mReserva de '%s' em %s confirmada.\33[0m\n", response->system_response.take.title,
				  counter_id);
			free(response->system_response.take.cover);
			
			pthread_mutex_unlock(&Write_cover);
		}
		
		free(response);
	}
	
	sys_lst_destroy(results);
	
	pthread_mutex_lock(&Take_mutex);
	Take_thread_status[i] = 1;
	pthread_mutex_unlock(&Take_mutex);
	
	return NULL;
}


static int take_threads_finished() {
	int finished = 1;
	
	for (int i = 0; i < Take_nr; ++i) {
		finished = finished && Take_thread_status[i];
	}
	
	return finished;
}


void process_take(char *command) {
	printf("\33[1m[process_client]\33[0m take\n");
	char id[MAX_BOOK_ID_SIZE + 2] = NOSTR;
	char counter[MAX_COUNTER_ID_SIZE] = NOSTR;
	char titlec[MAX_TITLES_SIZE] = NOSTR;
	req_list_t* req_list = req_lst_new();
	req_lst_iitem_t* free_list[MAX_NR_SERVERS];
	Take_nr = 0;
	Reserves_confirmed = 1;
	
	//TODO: T(TakeMax).expire case. print ids with no confirmation
	//TODO: T(TakeMax).start
	memset(&Take_thread_status, 0, MAX_TITLES_SIZE);
	
	for(int i = 0; i < strlen(command) - 5;) {
		sscanf(command + 5 + i, "%s", id);
	
		strncpy(counter, id, MAX_COUNTER_ID_SIZE - 1);
		counter[MAX_COUNTER_ID_SIZE - 1] = '\0';
		strncpy(titlec, id + MAX_COUNTER_ID_SIZE, MAX_BOOKNAME_SIZE - 1);
		
		// check if cover already exists
		FILE *fp;
		char cover_filename[MAX_FILENAME_SIZE];
		
		strcpy(cover_filename, "./");
		strcpy(cover_filename + strlen(cover_filename), titlec);
		strcpy(cover_filename + strlen(cover_filename), COVER_EXTENSION);
		
		if (id[MAX_COUNTER_ID_SIZE + MAX_BOOKNAME_SIZE - 1] == '/') {
			if ((fp = fopen(cover_filename, "r")) == NULL) {
				titlec[MAX_BOOKNAME_SIZE - 1] = '_';
				titlec[MAX_BOOKNAME_SIZE] = '1';
				titlec[MAX_BOOKNAME_SIZE + 1] = '\0';
			}
			else {
				titlec[MAX_BOOKNAME_SIZE - 1] = '_';
				titlec[MAX_BOOKNAME_SIZE] = '0';
				titlec[MAX_BOOKNAME_SIZE + 1] = '\0';
				fclose(fp);
			}
			i += MAX_BOOK_ID_SIZE - 1 + 2;
		}
		else {
			titlec[MAX_BOOKNAME_SIZE - 1] = '_';
			titlec[MAX_BOOKNAME_SIZE] = '0';
			titlec[MAX_BOOKNAME_SIZE + 1] = '\0';
			i += MAX_BOOK_ID_SIZE - 1;
		}
		
		req_lst_insert(req_list, counter, titlec);
	}
	
	thread_take_arg_t thread_arguments[MAX_NR_SERVERS];
	req_lst_iitem_t* request;
	for (Take_nr = 0; (request = req_lst_pop(req_list)) != NULL; ++Take_nr) {
		int port;
		char address[MAX_ADDRESS_SIZE];
		
		servers_get(request->counter, address, &port);		
		thread_arguments[Take_nr].titlecs = request->titles;
		thread_arguments[Take_nr].address = address;
		thread_arguments[Take_nr].port = port;
		pthread_create(&Take_threads[Take_nr], NULL, take_thread, &thread_arguments[Take_nr]);
		free_list[Take_nr] = request;
	}
	
	while (1) {
		pthread_mutex_lock(&Take_mutex);
		for (int j = 0; j < Take_nr; ++j) {
			if (Take_thread_status[j] == 1) {
				pthread_join(Take_threads[j], NULL);
				Take_thread_status[j] = 2;
			}
		}
		if (take_threads_finished()) {
			pthread_mutex_unlock(&Take_mutex);
			break;
		}
		pthread_mutex_unlock(&Take_mutex);
	}

	//TODO: T(TakeMax).stop
	for (int i = 0; i < Take_nr; ++i) {
		free(free_list[i]);
	}	

	if (Reserves_confirmed) {
		printf("\33[1mReservas confirmadas.\33[0m\n");
	} else {
		printf("\33[1mNem todas as reservas foram confirmadas.\33[0m\n");
	}
	req_lst_destroy(req_list);
}


void process_cancel(char *command) {
	printf("\33[1m[process_client]\33[0m cancel\n");
	char id[MAX_BOOK_ID_SIZE] = NOSTR;
	char counter[MAX_COUNTER_ID_SIZE] = NOSTR;
	char title[MAX_TITLES_SIZE] = NOSTR;
	req_list_t* req_list = req_lst_new();
	
	for(int i = 0; i < strlen(command) && sscanf(command + 7 + i, "%s ", id) != EOF; i += MAX_BOOK_ID_SIZE - 1) {
		strncpy(counter, id, MAX_COUNTER_ID_SIZE - 1);
		counter[MAX_COUNTER_ID_SIZE - 1] = '\0';
		strncpy(title, id + MAX_COUNTER_ID_SIZE, MAX_BOOKNAME_SIZE - 1);
		title[MAX_BOOKNAME_SIZE - 1] = '\0';
		req_lst_insert(req_list, counter, title);
	}
	
	req_lst_iitem_t* request;
	while ((request = req_lst_pop(req_list)) != NULL) {
		int port;
		char address[MAX_ADDRESS_SIZE];
		servers_get(request->counter, address, &port);
		
		bs_cancel(address, port, Username, request->titles);
		free(request);
	}
	
	req_lst_destroy(req_list);
}


void process_release(char* command) {
	printf("\33[1m[process_client]\33[0m release\n");
	// TODO: T(ReleaseMax).start
	
	char id[MAX_BOOK_ID_SIZE] = NOSTR;
	char counter[MAX_COUNTER_ID_SIZE] = NOSTR;
	char title[MAX_TITLES_SIZE] = NOSTR;
	req_list_t* req_list = req_lst_new();
	sys_list_t* results;
		
	// separates all titles into the respective server
	for(int i = 0; i < strlen(command) && sscanf(command + 7 + i, "%s ", id) != EOF; i += MAX_BOOK_ID_SIZE - 1) {
		strncpy(counter, id, MAX_COUNTER_ID_SIZE - 1);
		counter[MAX_COUNTER_ID_SIZE - 1] = '\0';
		strncpy(title, id + MAX_COUNTER_ID_SIZE, MAX_BOOKNAME_SIZE - 1);
		title[MAX_BOOKNAME_SIZE - 1] = '\0';
		req_lst_insert(req_list, counter, title);
	}
	
	// contacts each server to release the books
	req_lst_iitem_t* request;
	char books_to_return[MAX_TITLES_SIZE] = NOSTR;
	while ((request = req_lst_pop(req_list)) != NULL) {
		int makeavailable = 0;
		int port;
		char address[MAX_ADDRESS_SIZE];
		
		servers_get(request->counter, address, &port);
		
		if (strcmp(request->counter, Pc_id) == 0) {
			makeavailable = 1;
		}
		
		if (bs_release(address, port, Username, request->titles, makeavailable, &results) == STAT_ERROR) {
			//TODO
			return;
		}
		
		sys_lst_iitem_t* response;
		while ((response = sys_lst_pop(results)) != NULL) {
			if (response->status == OK && !makeavailable) {
				strcat(books_to_return, response->system_response.release.title);
				strcat(books_to_return, " ");
				vprintf("\33[1m%s libertado em %s mas nao devolvido.\33[0m\n",
						response->system_response.release.title, request->counter);
			} else if (response->status == ERROR) {
				vprintf("\33[1m%s\33[0m\n", response->system_response.release.reason);
			} else if (makeavailable) {
				vprintf("\33[1m%s devolvido ao balcao predefinido.\33[0m\n",
					response->system_response.release.title);
			}
			
			free(response);
		}
		sys_lst_destroy(results);
		free(request);
	}
	req_lst_destroy(req_list);
	
	//TODO: T(ReleaseMax).stop
	//TODO: T(ReleaseMax).start
	
	// if there are still books to return, return them to the preferred counter
	sys_list_t* response;
	if (strlen(books_to_return) != 0) {
		vprintf("\33[1mLivros a doar ao balcao predefinido: %s\33[0m\n", books_to_return);
		bs_return(Pc_address, Pc_port, books_to_return, &response);
		printf("\33[1mDoacao efectuada com sucesso\33[0m\n");
	}
	
	//TODO: if T(ReleaseMax).expires
	//printf("\33[1mInsucesso na doacao\33[0m\n");
}


void process_quit(char *command) {
	printf("\33[1m[process_client]\33[0m quit\n");
	vprintf("\33[1m[process_client]\33[0m client terminating.\n");
	exit(0);
}
