/* ************************************************************************** */
/* ***************************** main thread ******************************** */
/* ************************************************************************** */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>

#include "MPSServer_defs.h"

#define DEFINE_VARIABLES
	#include "MPSServer_vars.h"
#undef DEFINE_VARIABLES

int check_client()
{
	u_char OpTp;
	u_long ConnectString;
	u_long ClientID;
	process_data *process;
	process_search_data *process_search;

	wanted_pid = 0;

	if ( !td_read_u_char(accepted_socket, &OpTp) )
	{
		return -1;
	}

	if ( OpTp != OpTp_CONNECT )
	{
		return -2;
	}

	if ( !td_read_u_long(accepted_socket, &ConnectString) )
	{
		return -3;
	}

	if ( ConnectString != CONNECTION_STRING )
	{
		return -4;
	}

	if ( !td_read_u_long(accepted_socket, &ClientID) )
	{
		return -5;
	}

	wanted_pid = ClientID;

	pthread_mutex_lock(&thread_mutex);

	process = NULL;

	while ( ClientID == 0 )
	{
		pid_conn_sequencer++;
		ClientID = pid_conn_sequencer;

		process_search = process_list_FindEntryEqual(&process_list, ClientID);

		if ( process_search != NULL )
		{
			process = process_search->process;
			if ( process->cdata != NULL )
			{
				ClientID = 0;
				process = NULL;
			}
		}
	}

	if ( process == NULL )
	{
		process_search = process_list_FindEntryEqual(&process_list, ClientID);
		if ( process_search != NULL ) process = process_search->process;
	}

	if ( process == NULL )
	{
		pthread_mutex_unlock(&thread_mutex);

		return -6;
	}
	else if ( process->cdata != NULL )
	{
		pthread_mutex_unlock(&thread_mutex);

		return -7;
	}

	pthread_mutex_unlock(&thread_mutex);

	accepted_pid = ClientID;

	return 0;
}

int create_thread()
{
	thread_data *new_thread_data;
	pthread_attr_t thread_attr;
	process_data *process;
	process_search_data *process_search;
	int found;
	u_char OpTp;

	td_buffer_struct td_buffer;

	found = -1;

	td_buffer.pos = 0;
	td_buffer.size = 1460;
	td_buffer.buf = malloc(td_buffer.size);

	if (td_buffer.buf == NULL) return -1;

	pthread_mutex_lock(&thread_mutex);

	process_search = process_list_FindEntryEqual(&process_list, accepted_pid);

	if ( process_search == NULL )
	{
		pthread_mutex_unlock(&thread_mutex);
		free(td_buffer.buf);
		return -2;
	}

	process = process_search->process;

/* find a thread which can accept a client */
	if ( first_thread_data != NULL )
	{
		new_thread_data = first_thread_data;
		do
		{
			if (new_thread_data->exit_thread == 0 &&
				new_thread_data->num_conn_sockets < MAX_CLIENTS_PER_THREAD
				)
			{
				for (found = 0; found < MAX_CLIENTS_PER_THREAD; found++)
				{
					if ( new_thread_data->conn_sockets[found].conn_socket == 0 )
					{
						break;
					}
				}
				break;
			}

			new_thread_data = new_thread_data->next;
		} while ( new_thread_data != first_thread_data );
	} /* if ( first_thread_data != NULL ) */

/* if not found, then create a thread */
	if ( found == -1 )
	{
/* prepare thread attributes */
		if ( pthread_attr_init(&thread_attr) )
		{
			pthread_mutex_unlock(&thread_mutex);
			free(td_buffer.buf);
			return -3;
		}

		pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);

		new_thread_data = (thread_data *) malloc(sizeof(thread_data));

		if (new_thread_data == NULL)
		{
			pthread_attr_destroy(&thread_attr);
			pthread_mutex_unlock(&thread_mutex);
			free(td_buffer.buf);
			return -4;
		}

/* initialize thread data */
		for (found = 0; found < MAX_CLIENTS_PER_THREAD; found++)
		{
			new_thread_data->conn_sockets[found].conn_socket = 0;
		}
		found = 0;
		new_thread_data->num_conn_sockets = 0;
/*		new_thread_data->thread_ptr = 0;*/
		new_thread_data->exit_thread = 0;
		new_thread_data->next = NULL;
		new_thread_data->prev = NULL;


/* create thread */
		if ( pthread_create(&(new_thread_data->thread_ptr), &thread_attr, (void *(*)(void *)) &conn_thread, new_thread_data) )
		{
			free(new_thread_data);

			pthread_attr_destroy(&thread_attr);
			pthread_mutex_unlock(&thread_mutex);

			free(td_buffer.buf);
			return -5;
		}

		num_threads++;

/* add thread to thread list */
		if (first_thread_data == NULL)
		{
			first_thread_data = new_thread_data;
			first_thread_data->next = first_thread_data;
			first_thread_data->prev = first_thread_data;
		}
		else
		{
			new_thread_data->next = first_thread_data;
			new_thread_data->prev = first_thread_data->prev;
			first_thread_data->prev = new_thread_data;
			new_thread_data->prev->next = new_thread_data;
		}

		pthread_attr_destroy(&thread_attr);
	} /* if ( found != -1 ) */

/* add client to thread */

	memcpy(&(new_thread_data->conn_sockets[found].conn_name), &accepted_name, MIN(sizeof(new_thread_data->conn_sockets[found].conn_name), sizeof(accepted_name)));
	new_thread_data->conn_sockets[found].conn_socket = accepted_socket;
	new_thread_data->conn_sockets[found].process_id = accepted_pid;
	new_thread_data->conn_sockets[found].process = process;
	memcpy(&(new_thread_data->conn_sockets[found].td_buffer), &td_buffer, sizeof(td_buffer_struct));
	new_thread_data->num_conn_sockets++;
	num_clients++;

	process->tdata = new_thread_data;
	process->cdata = &(new_thread_data->conn_sockets[found]);

/* reply to client */
	OpTp = OpTp_ACCEPT_CONNECT;

	td_write_u_char(accepted_socket, &OpTp, &td_buffer);
	td_write_u_long(accepted_socket, &accepted_pid, &td_buffer);
	td_flush(accepted_socket, &td_buffer);

	pthread_mutex_unlock(&thread_mutex);

	return 0;
}

void deinitialize_values()
{
#if defined(__MINGW32__)
	WSACleanup( );
#endif

	write_log_deinitialization();

	operation_list_DeleteAll(&operation_send_list);
	operation_list_DeleteAll(&operation_recv_list);
	process_list_DeleteAll(&process_list);
}

void initialize_values()
{
	listening_port = 19792;
	FD_ZERO(&sockets);
	tcp_socket = 0;
	exit_server = 0;
	num_threads = 0;
	num_clients = 0;
	first_thread_data = NULL;
	exit_semaphore_waiting = 0;
	mid_sequencer = 0;
	oid_sequencer = 0;
	pid_sequencer = 1;
	pid_conn_sequencer = 0;
	operation_send_list = NULL;
	operation_recv_list = NULL;
	process_list = NULL;
	process_list_Insert(&process_list, 1, 1);

	write_log_initialization();

#if defined(__MINGW32__)
	wVersionRequested = MAKEWORD( 1, 0 );
	WSAStartup( wVersionRequested, &wsaData );
#endif
}

int make_socket (unsigned short int port)
{
	int sock;
	struct sockaddr_in name;

	/* Create the socket. */
	sock = socket (PF_INET, SOCK_STREAM, 0);
	if (sock == -1)
	{
		return -1;
	}

	/* Give the socket a name. */
	name.sin_family = AF_INET;
	name.sin_port = htons (port);
	name.sin_addr.s_addr = htonl (INADDR_ANY);
	if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
	{
		closesocket(sock);
		return -1;
    }

	return sock;
}

void shutdown_threads()
{
	thread_data *cur_thread_data;

	pthread_mutex_lock(&thread_mutex);
	if ( num_threads == 0 )
	{
		pthread_mutex_unlock(&thread_mutex);
		return;
	}

	cur_thread_data = first_thread_data;

	do
	{
		cur_thread_data->exit_thread = 1;
		cur_thread_data = cur_thread_data->next;
	} while ( cur_thread_data != first_thread_data );

	exit_semaphore_waiting = 1;

	pthread_mutex_unlock(&thread_mutex);

	sem_wait(&exit_semaphore);

/* wait threads exit */
	pthread_mutex_lock(&thread_mutex);
	pthread_mutex_unlock(&thread_mutex);
}

void signal_handler(int sig)
{
	if (sig == SIGINT) exit_server = 1;
}

int main (int argc, char *argv[])
{

	printf("Starting MPS server...\n");

	initialize_values();

	tcp_socket = make_socket(listening_port);
	if ( tcp_socket == -1 )
	{
		write_log_server_startup(0, "unable to open socket");
		deinitialize_values();
		fprintf(stderr, "Error: unable to open socket\n");
		return 1;
	}

	if ( listen(tcp_socket, SOMAXCONN) )
	{
		closesocket(tcp_socket);
		write_log_server_startup(0, "unable to listen on port");
		deinitialize_values();
		fprintf(stderr, "Error: unable to listen on port: %i\n", (unsigned int) listening_port);
		return 1;
	}

	if ( pthread_mutex_init(&thread_mutex, NULL) )
	{
		closesocket(tcp_socket);
		write_log_server_startup(0, "unable to initialize mutex");
		deinitialize_values();
		fprintf(stderr, "Error: unable to initialize mutex\n");
		return 1;
	}

	if ( sem_init(&exit_semaphore, 0, 0) )
	{
		pthread_mutex_destroy(&thread_mutex);
		closesocket(tcp_socket);
		write_log_server_startup(0, "unable to initialize semaphore");
		deinitialize_values();
		fprintf(stderr, "Error: unable to initialize semaphore\n");
		return 1;
	}

	write_log_server_startup(1, NULL);

	signal(SIGINT, &signal_handler);

	printf("MPS server started - listening for connections on port %i\n", (unsigned int) listening_port);

	while ( !exit_server )
	{
		listen_timeout.tv_sec = 1;
		listen_timeout.tv_usec = 0;
		FD_ZERO(&sockets);
		FD_SET(tcp_socket, &sockets);

		listen_return = select(tcp_socket + 1, &sockets, NULL, NULL, &listen_timeout);

		if ( listen_return == -1) break;
		if ( listen_return == 0) continue;

		printf("Incomming connection ...\n");

		accepted_socket_size = sizeof(accepted_name);
		accepted_socket = accept(tcp_socket, &accepted_name, &accepted_socket_size);

		if ( accepted_socket == -1 )
		{
			write_log_client_connect(0, -1, "unable to accept connection");
			fprintf(stderr, "Error: unable to accept connection\n");
			continue;
		}

		if ( check_client() )
		{
			u_char OpTp;

			OpTp = OpTp_REJECT_CONNECT;

			td_write_u_char(accepted_socket, &OpTp, NULL);

			shutdown(accepted_socket, SHUT_RDWR);
			closesocket(accepted_socket);

			write_log_client_connect(0, (wanted_pid == 0)?-1:wanted_pid, "unable to accept client");
			fprintf(stderr, "Error: unable to accept client\n");
			continue;
		}

		if ( create_thread() )
		{
			u_char OpTp;

			OpTp = OpTp_REJECT_CONNECT;

			td_write_u_char(accepted_socket, &OpTp, NULL);

			shutdown(accepted_socket, SHUT_RDWR);
			closesocket(accepted_socket);

			write_log_client_connect(0, accepted_pid, "unable to create thread for incoming connection");
			fprintf(stderr, "Error: unable to create thread for incoming connection\n");
			continue;
		}

		write_log_client_connect(1, accepted_pid, NULL);
	}

	printf("MPS server shutting down...\n");

	shutdown(tcp_socket, SHUT_RDWR);

	closesocket(tcp_socket);

	shutdown_threads();

	sem_destroy(&exit_semaphore);
	pthread_mutex_destroy(&thread_mutex);

	write_log_server_shutdown();

	deinitialize_values();

	return 0;
}
