/* 
 * Book Share Server
 * 
 * server.c
 *
 * Server implementing Book Share services.
 * 
 */

#define _POSIX_SOURCE

#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>

#include <config.h>
#include <bs_proto.h>
#include <response_list.h>
#include "bs.h"


/*
 * Book Share Services
 *
 * all services are registered in a global table which is
 * used during request resolution to find the handler that
 * serves the request
 *
 * service handlers are implemented in bs.c
 */


struct {
	bs_pdu_type_t type;
	bs_handler_t handler;
} Service[NUM_REQ_HANDLERS] = {
	{REQ_GIVE, bs_give},
	{REQ_SEARCH, bs_search},
	{REQ_CANCEL, bs_cancel},
	{REQ_TAKE, bs_take},
	{REQ_RELEASE, bs_release},
	{REQ_RETURN, bs_return}
};


// type of a server request
typedef struct {
	int sockfd, reqsz;
	struct sockaddr_in cliaddr;
	socklen_t clilen;
	bs_pdu_req_t req;
} srv_req_t;


/*
 * Internal private global variables
 */

// requests buffer
static srv_req_t Request[NUM_REQ_SLOTS];

// threads used to handle the requests
static pthread_t Request_Handlers[NUM_HANDLER_THR];

// 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_full;

// controls access to the buffer
static pthread_cond_t Cond_not_empty;



/*
 * Internal auxiliary functions for server request management
 */


static void srv_req_insert(srv_req_t *srv_req) {
	static int next = 0;
	
	pthread_mutex_lock(&Mutex);
	
	while (Pending_requests >= NUM_REQ_SLOTS) {
		pthread_cond_wait(&Cond_not_full, &Mutex);
	}
	
	++Pending_requests;
	memcpy(&Request[next], srv_req, sizeof(srv_req_t));
	++next;
	next %= NUM_REQ_SLOTS;
	
	pthread_mutex_unlock(&Mutex);
	pthread_cond_signal(&Cond_not_empty);
}


static void srv_req_remove(srv_req_t *srv_req) {
	static int next = 0;
	
	pthread_mutex_lock(&Mutex);
	
	while (Pending_requests == 0) {
		pthread_cond_wait(&Cond_not_empty, &Mutex);
	}
	
	memcpy(srv_req, &Request[next], sizeof(srv_req_t));
	++next;
	next %= NUM_REQ_SLOTS;
	--Pending_requests;
	
	pthread_mutex_unlock(&Mutex);
	pthread_cond_signal(&Cond_not_full);
}


/*
 * Internal auxiliary funtions for communication management
 */


void srv_udp_init_socket(int *sockfd) {
/*	struct sockaddr_in servaddr;
	
	// creates socket datagram
	if ((*sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
		printf("\33[1m[bs_srv]\33[0m socket error: %s.\n", strerror(errno));
		exit(-1);
	}
	
	// structure address cleaning
	bzero(&servaddr, sizeof(servaddr));
	
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERVER_PORT);
	
	// binds socket to address
	if (bind(*sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
		printf("\33[1m[bs_srv]\33[0m bind error: %s.\n", strerror(errno));
		exit(-1);
	}*/

	struct addrinfo hints, *servinfo, *p;
	int rv;
	char port[MAX_PORT_SIZE];
	sprintf(port, "%d", SERVER_PORT);
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET; // use AF_INET6 to force IPv6
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE; // use my IP address
	if ((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) {
		printf("\33[1m[bs_srv]\33[0m getaddrinfo error: %s.\n", gai_strerror(rv));
		exit(-1);
	}
	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((*sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			printf("\33[1m[bs_srv]\33[0m socket error: %s.\n", strerror(errno));
			continue;
		}
		if (bind(*sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(*sockfd);
			printf("\33[1m[bs_srv]\33[0m bind error: %s.\n", strerror(errno));
			continue;
		}
		break; // if we get here, we must have connected successfully
	}
	if (p == NULL) {
		// looped off the end of the list with no successful bind
		printf("\33[1m[bs_srv]\33[0m failed to bind socket.\n");
		exit(-1);
	}
	freeaddrinfo(servinfo); // all done with this structure

	// set socket as non blocking
	int flags = fcntl(*sockfd, F_GETFL);
	fcntl(*sockfd, F_SETFL, flags | O_NONBLOCK);
}


int srv_udp_recv_request(int sockfd, bs_pdu_req_t *req,
	struct sockaddr_in *cliaddr, socklen_t *clilen) {
	*clilen = sizeof(*cliaddr);
	
	int reqsz = recvfrom(sockfd, (void*)req, sizeof(*req), 0, (struct sockaddr *)cliaddr, clilen);
	
	if (reqsz == 0) {
		printf("\33[1m[bs_srv]\33[0m request error.\n");
		return 0;
	}
	
	if (reqsz == -1) {
		// no message available to read
		return 0;
	}
	
	if (reqsz < 0) {
		printf("\33[1m[bs_srv]\33[0m recvfrom error: %s.\n", strerror(errno));
		return 0;
	}
	
	printf("\33[1m[bs_srv]\33[0m received udp request.\n");
	
	return reqsz;
}


void srv_udp_send_response(int sockfd, bs_pdu_res_t *res, int ressz,
	struct sockaddr_in *cliaddr, socklen_t clilen) {
	int status;
	
	do {
		// try to send the response
		status = sendto(sockfd, res, ressz, 0, (struct sockaddr *)cliaddr, clilen);
	} while (status == -1 && errno == EAGAIN);
	
	if (status < 0) {
		printf("\33[1m[bs_srv]\33[0m sendto error: %s.\n", strerror(errno));
		return;
	}
	
	if (status != ressz) {
		printf("\33[1m[bs_srv]\33[0m message size mismatch.\n");
	}
	
	printf("\33[1m[bs_srv]\33[0m sent udp response.\n");
}


void srv_tcp_init_socket(int *sockfd) {
/*	struct sockaddr_in servaddr;
	
	// creates socket stream
	if ((*sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		printf("\33[1m[bs_srv]\33[0m socket error: %s.\n", strerror(errno));
		exit(-1);
	}
	
	// structure address cleaning
	bzero(&servaddr, sizeof(servaddr));
	
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERVER_PORT);
	
	int yes = 1;
	
	if (setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		printf("\33[1m[bs_srv]\33[0m setsockopt error: %s.\n", strerror(errno));
		exit(-1);
	}
	
	// binds socket to address
	if (bind(*sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
		printf("\33[1m[bs_srv]\33[0m bind error: %s.\n", strerror(errno));
		exit(-1);
	}*/

	struct addrinfo hints, *servinfo, *p;
	int rv, yes = 1;
	char port[MAX_PORT_SIZE];
	sprintf(port, "%d", SERVER_PORT);
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET; // use AF_INET6 to force IPv6
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP address
	if ((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) {
		printf("\33[1m[bs_srv]\33[0m getaddrinfo error: %s.\n", gai_strerror(rv));
		exit(-1);
	}
	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((*sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
			printf("\33[1m[bs_srv]\33[0m socket error: %s.\n", strerror(errno));
			continue;
		}
		if (setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
			printf("\33[1m[bs_srv]\33[0m setsockopt error: %s.\n", strerror(errno));
			continue;
		}
		if (bind(*sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(*sockfd);
			printf("\33[1m[bs_srv]\33[0m bind error: %s.\n", strerror(errno));
			continue;
		}
		break; // if we get here, we must have connected successfully
	}
	if (p == NULL) {
		// looped off the end of the list with no successful bind
		printf("\33[1m[bs_srv]\33[0m failed to bind socket.\n");
		exit(-1);
	}
	freeaddrinfo(servinfo); // all done with this structure
	
	// set socket as non blocking
	int flags = fcntl(*sockfd, F_GETFL);
	fcntl(*sockfd, F_SETFL, flags | O_NONBLOCK);
}


int srv_tcp_recv_request(int sockfd, bs_pdu_req_t *req) {	
	int len = 0;
	int lensz = 0;
	int reqsz = 0;
		
	while (1) {
		int sz;
		
		if (lensz < sizeof(len)) {
			sz = recv(sockfd, (void*)(((char*)&len) + lensz), sizeof(len) - lensz, 0);
		} else {
			sz = recv(sockfd, (void*)((char*)req + reqsz), len - reqsz, 0);
		}
		
		if (sz == -1 && errno == EAGAIN) {
			continue;
		} else if (sz < 0) {
			printf("\33[1m[bs_srv]\33[0m read error: %s.\n", strerror(errno));
			return 0;
		} else if (sz == 0) {
			return 0;
		} else {
			if (lensz < sizeof(len)) {
				lensz += sz;
				continue;
			}
			
			reqsz += sz;
			
			if (reqsz == len) {
				break;
			}
		}
	}
	
	printf("\33[1m[bs_srv]\33[0m received tcp request.\n");
	return reqsz;
}


void srv_tcp_send_response(int sockfd, bs_pdu_res_t *res, int ressz) {
	int lensz = 0;
	int status = 0;
	
	while (1) {
		int sz = 0;
		
		if (lensz < sizeof(ressz)) {
			sz = send(sockfd, (void*)(((char*)&ressz) + lensz), sizeof(ressz) - lensz, 0);
		} else {
			sz = send(sockfd, (void*)((char*)res + status), ressz - status, 0);
		}
			
		if (sz == -1 && errno == EAGAIN) {
			continue;
		} else if (sz < 0) {
			printf("\33[1m[bs_srv]\33[0m write error: %s.\n", strerror(errno));
			return;
		} else if (sz == 0) {
			if (status != ressz) {
				printf("\33[1m[bs_srv]\33[0m message size mismatch.\n");
			}
			return;
		} else {
			
			if (lensz < sizeof(ressz)) {
				lensz += sz;
				continue;
			}
			
			printf("\33[1m[bs_srv]\33[0m sent tcp datagram with %d bytes.\n", sz);
			
			status += sz;
			
			if (status == ressz) {
				printf("\33[1m[bs_srv]\33[0m sent tcp response.\n");
				return;
			}
		}
	}
}


void* srv_handler_request(void * arg) {
	int req_i;
	srv_req_t srv_req;
	res_list_t *res_list;
	
	while (1) {
		// remove request from buffer
		srv_req_remove(&srv_req);
		
		// find request handler
		req_i = -1;
		for (int i = 0; i < NUM_REQ_HANDLERS; i++) {
			if (srv_req.req.type == Service[i].type) {
				req_i = i;
				break;
			}
		}
		
		// serve the request
		Service[req_i].handler(&srv_req.req, srv_req.reqsz, &res_list);
		
		response_t *res;
		int tcp = 0;
		
		while ((res = res_lst_pop(res_list)) != NULL) {
			// send response to client
			if (req_i == REQ_GIVE || req_i == REQ_SEARCH || req_i == REQ_CANCEL) {
				srv_udp_send_response(srv_req.sockfd, res->res, res->ressz,
					&srv_req.cliaddr, srv_req.clilen);
			} else {
				tcp = 1;
				srv_tcp_send_response(srv_req.sockfd, res->res, res->ressz);
			}
			
			bs_free_response(res);
		}
		
		res_lst_destroy(res_list);
		
		if (tcp == 1) {
			close(srv_req.sockfd);
		}
	}
	
	return NULL;
}


void thread_yield() {
	const int sthread_select_sec_timeout = 0;
	const int sthread_select_usec_timeout = 1;
	fd_set fdset;
	struct timeval select_timeout;
	FD_ZERO(&fdset);
	select_timeout.tv_sec = sthread_select_sec_timeout;
	select_timeout.tv_usec = sthread_select_usec_timeout;
	select(0, &fdset, &fdset, &fdset, &select_timeout);
}


void *udp_server(void *arg) {
	srv_req_t srv_req;
	
	// initialize communications
	srv_udp_init_socket(&srv_req.sockfd);
	
	while (1) {		
		// read request from client
		if ((srv_req.reqsz = srv_udp_recv_request(srv_req.sockfd, &srv_req.req, &srv_req.cliaddr,
			&srv_req.clilen)) == 0) {
			thread_yield();
			continue;
		}
		
		// insert request in buffer
		srv_req_insert(&srv_req);
	}
}


void *tcp_server(void *arg) {
	int srv_sockfd;
	srv_req_t srv_req;
	
	// initialize communications
	srv_tcp_init_socket(&srv_sockfd);
	
	/* Start accepting connections */
	listen (srv_sockfd, NUM_TCP_CONNECTIONS);
	
	while (1) {
		srv_req.clilen = sizeof(srv_req.cliaddr);
		
		srv_req.sockfd = accept(srv_sockfd, (struct sockaddr *) &srv_req.cliaddr,
			&srv_req.clilen);
		
		if (srv_req.sockfd == -1) {
			continue;
		}
		
		// read request from client
		if ((srv_req.reqsz = srv_tcp_recv_request(srv_req.sockfd, &srv_req.req)) == 0) {
			thread_yield();
			continue;
		}
		
		// insert request in buffer
		srv_req_insert(&srv_req);
	}
}


/*
 * Book Share server main
 */


int main(int argc, char **argv) {
	pthread_t tcp_thread, udp_thread;
	
	printf("\33[1m[bs_srv]\33[0m starting server.\n");
	
	if (argc > 3) {
		printf("\33[1m[bs_srv]\33[0m invalid arguments error, example: Balcony Users.dat Titles.dat\n");
		exit(-1);
	}
	
	// initialize control structures
	pthread_mutex_init(&Mutex, NULL);
	pthread_cond_init(&Cond_not_full, NULL);
	pthread_cond_init(&Cond_not_empty, NULL);
	
	// initialize the request handling threads
	for (int i = 0; i < NUM_HANDLER_THR; i++) {
		if (pthread_create(&(Request_Handlers[i]), NULL, srv_handler_request, NULL) != 0) {
			printf("\33[1m[bs_srv]\33[0m thread creating error.\n");
			exit(-1);
		}
	}
	
	// initialize BS layer
	bs_init(argc, argv);
	
	// initialize tcp and udp listeners
	if (pthread_create(&tcp_thread, NULL, tcp_server, NULL) != 0) {
		printf("\33[1m[bs_srv]\33[0m tcp thread creating error.\n");
		exit(-1);
	}
	if (pthread_create(&udp_thread, NULL, udp_server, NULL) != 0) {
		printf("\33[1m[bs_srv]\33[0m udp thread creating error.\n");
		exit(-1);
	}

	printf("\33[1m[bs_srv]\33[0m server initialized.\n");
	
	pthread_join(tcp_thread, NULL);
	pthread_join(udp_thread, NULL);
	
	for (int i = 0; i < NUM_HANDLER_THR; i++) {
		pthread_join(Request_Handlers[i], NULL);
	}
	
	return 0;
}
