/* 
 * Bookshare
 * 
 */

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

#include <config.h>
#include <strdup.h>
#include "colors.h"
#include "str_list.h"
#include "process.h"
#include "servers.h"


// maximum input size
#define MAX_LINE_SIZE 1024


/*
 * Internal private global variables
 */

// stores requests yet to handle
static str_list_t *Request_list;

// counts request yet to handle
static int Pending_requests;

// controls access to the buffer
static pthread_mutex_t Mutex;

// controls access to the buffer
static pthread_cond_t Cond_not_empty;

// switch
static int V_flag;

// username
static char Username[MAX_USERNAME_SIZE];

// password
static char Password[MAX_PASSWORD_SIZE];

// counter filename
static char Counter[MAX_FILENAME_SIZE];

// internt codes
enum {QUIT = 0, GIVE = 1, SEARCH = 2, TAKE = 3, CANCEL = 4, RELEASE = 5, ERROR = -1};


/*
 * Internal auxiliary functions for input management
 */


static void client_req_insert(char *client_req) {	
	pthread_mutex_lock(&Mutex);
	
	str_lst_insert(Request_list, client_req);
	++Pending_requests;
	
	pthread_mutex_unlock(&Mutex);
	pthread_cond_signal(&Cond_not_empty);
}


static char *client_req_remove() {	
	pthread_mutex_lock(&Mutex);
	
	while (Pending_requests == 0) {
		pthread_cond_wait(&Cond_not_empty, &Mutex);
	}
	
	char *client_req = str_lst_remove(Request_list);
	--Pending_requests;
	
	pthread_mutex_unlock(&Mutex);
	
	return client_req;
}


/*
 * Auxiliary functions to process input
 */


void check_arguments(int argc, char **argv) {
	if (argc == 1) {
		V_flag = 0; 
	} else if (strcmp("/v", argv[1]) == 0) {
		V_flag = 1;
	}
	
	if ((argc == 2 && V_flag == 0) || (argc == 3 && V_flag == 1) || (argc == 5 && V_flag == 0)
		|| argc > 5) {
		printf("\33[1m[bs_client]\33[0m invalid arguments error, example: BookShare User Password MyBalcony\n");
		exit(-1);
	}
	
	strncpy(Username, DEFAULT_USERNAME, MAX_USERNAME_SIZE - 1);
	strncpy(Password, DEFAULT_PASSWORD, MAX_USERNAME_SIZE - 1);
	strncpy(Counter, DEFAULT_COUNTER, MAX_USERNAME_SIZE - 1);

	if (argc >= 3) {
		int offset = V_flag;
		
		strncpy(Username, argv[1 + offset], MAX_USERNAME_SIZE - 1);
		strncpy(Password, argv[2 + offset], MAX_PASSWORD_SIZE - 1);
		
		if (argc == 5 || (argc == 4 && V_flag == 0)) {
			strncpy(Counter, argv[3 + offset], MAX_FILENAME_SIZE - 1);
		}
	}
	
	if (servers_new(Counter, V_flag) != 0) {
		printf("\33[1m[bs_client]\33[0m invalid default counter file.\n");
		exit(-1);
	}
}


int check_titles(char *titles) {
	int len = strlen(titles);
	
	if (((len + 1) % MAX_BOOKNAME_SIZE) != 0) {
		return -1;
	}
	
	for (int i = 0; i <= len; i += MAX_BOOKNAME_SIZE) {
		if (!isalnum(titles[i]) || !isalnum(titles[i + 1])
			|| (titles[i + 2] != ' ' && titles[i + 2] != '\0')) {
			return -1;
		}
	}
	
	return 0;
}


int check_ids(char *ids, int take_flag) {
	int len = strlen(ids);
	
	for (int i = 0; i <= len; i++) {
		for (int j = i; j <= i + MAX_COUNTER_ID_SIZE + MAX_BOOKNAME_SIZE - 1 && j <= len; j++) {
			if (j == len && j < i + MAX_COUNTER_ID_SIZE + MAX_BOOKNAME_SIZE - 1) {
				return -1;
			} else if (j == i + MAX_COUNTER_ID_SIZE - 1) {
				if (ids[j] != '-') {
					return -1;
				}
			} else if (j == i + MAX_COUNTER_ID_SIZE + MAX_BOOKNAME_SIZE - 1) {
				if (ids[j] == ' ' || ids[j] == '\0') {
					i = j;
					break;
				} else if (!take_flag || ids[j] != '/') {
					return -1;
				} else {
					if (j + 1 == len || ids[j + 1] != 'c') {
						return -1;
					} else if (j + 2 != len && ids[j + 2] != ' ') {
						return -1;
					}
					i = j + 2;
					break;
				}
			} else if (!isalnum(ids[j])) {
				return -1;
			}
		}
	}
		
	return 0;
}


int check_request(char *req) {
	char buffer[MAX_LINE_SIZE];
	int start_at = 0, number_args = 0;
	
	for (int i = 0; i < strlen(req); i++) {
		if (req[i] != ' ' && req[i] != '\t' && req[i] != '\n' && req[i] != '\r') {
			start_at = i;
			number_args++;
			break;
		}
	}

	for (int i = start_at, j = 0, prev_white = 0; i <= strlen(req); i++) {
		if (req[i] == ' ' || req[i] == '\t') {
			if (prev_white == 0) {
				buffer[j++] = ' ';
				prev_white = 1;
				number_args++;
			}
		} else if (req[i] != '\n' && req[i] != '\r') {
			buffer[j++] = req[i];
			prev_white = 0;
		}
	}
	
	int len = strlen(buffer);
	
	if (buffer[len - 1] == ' ') {
		number_args--;
		buffer[len - 1] = '\0';
		len--;
	}
	
	if (number_args == 0) {
		return ERROR;
	}
	
	if (number_args == 1) {
		if (strcmp(buffer, "Quit") == 0) {
			return QUIT;
		} else {
			return ERROR;
		}
	}
	if (strncmp(buffer, "Give ", 5) == 0) {
		if (check_titles(buffer + 5) == -1) {
			return ERROR;
		}
		return GIVE;
	} else	if (strncmp(buffer, "Search ", 7) == 0) {
		if (check_titles(buffer + 7) == -1) {
			return ERROR;
		}
		return SEARCH;
	} else if (strncmp(buffer, "Take ", 5) == 0) {
		if (check_ids(buffer + 5, 1) == -1) {
			return ERROR;
		}
		return TAKE;
	} else if (strncmp(buffer, "Cancel ", 7) == 0) {
		if (check_ids(buffer + 7, 0) == -1) {
			return ERROR;
		}
		return CANCEL;
	} else if (strncmp(buffer, "Release ", 8) == 0) {
		if (check_ids(buffer + 8, 0) == -1) {
			return ERROR;
		}
		return RELEASE;
	} else {
		return ERROR;
	}
}


void *client_handler_request(void *arg) {
	while (1) {		
		char *req = client_req_remove();
		int res = check_request(req);
		int len = strlen(req);
		req[len - 1] = '\0';
		switch (res) {
			case GIVE:
				process_give(req);
				break;
			case SEARCH:
				process_search(req);
				break;
			case TAKE:
				process_take(req);
				break;
			case CANCEL:
				process_cancel(req);
				break;
			case RELEASE:
				process_release(req);
				break;
			case QUIT:
				process_quit(req);
				free(req);
				exit(0);
				break;
			default:
				printf("\33[1m[bs_client]\33[0m Invalid input: %s\n", req);
		}
		free(req);
	}
	
	return NULL;
}


/*
 * Book Share client main
 */
 

int main(int argc, char **argv) {
	char buffer[MAX_LINE_SIZE];
	pthread_t thread;
	
	check_arguments(argc, argv);
	process_init(Username, Password, V_flag);
	
	vprintf("\33[1m[bs_client]\33[0m starting client.\n");
	
	Request_list = str_lst_new();
	
	// initialize control structures
	pthread_mutex_init(&Mutex, NULL);
	pthread_cond_init(&Cond_not_empty, NULL);
	
	pthread_create(&thread, NULL, client_handler_request, NULL);
	
	vprintf("\33[1m[bs_client]\33[0m client initialized.\n");
	
	while (1) {
		fgets(buffer, MAX_LINE_SIZE, stdin);
		
		if (strcmp(buffer, "Quit\n") == 0) {
			pthread_mutex_unlock(&Mutex);
			pthread_cond_signal(&Cond_not_empty);
			if (str_lst_insert_quit(Request_list) != 0) {
				printf("\33Aguarde: Release em curso...\33[0m\n");
			}
			pthread_mutex_unlock(&Mutex);
			pthread_cond_signal(&Cond_not_empty);
		}
		
		client_req_insert(strdup(buffer));
	}
	
	return 0;
}
