#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>

#define INT 1
#define FLOAT 2
#define BOOL 4
#define ALL_TYPES (INT | FLOAT | BOOL)
#define EMPTY 128

#define REQ_PUT 1
#define REQ_GET 2
#define REQ_POP 4

#define SERVER_PORT 9998

int sockfd = -1;
struct sockaddr_in client_addr;

unsigned char receiveBuffer[1024];

typedef struct Buffer {
	int len;
	unsigned char* data;
} Buffer;

typedef struct TupleContainer {
	Buffer tuple;
	struct TupleContainer* next;
} TupleContainer;

bool tuples_match(Buffer tuple1, Buffer tuple2) {
	unsigned char tuple1_name_size = *tuple1.data;
	++tuple1.data;
	unsigned char tuple2_name_size = *tuple2.data;
	++tuple2.data;
	if(tuple1_name_size != tuple2_name_size) {
		return false;
	}
	if(memcmp(tuple1.data, tuple2.data, tuple1_name_size)) {
		return false;
	}
	tuple1.data += tuple1_name_size;
	tuple2.data += tuple2_name_size;

	unsigned char tuple1_n_fields = *tuple1.data;
	++tuple1.data;
	unsigned char tuple2_n_fields = *tuple2.data;
	++tuple2.data;

	if(tuple1_n_fields != tuple2_n_fields) {
		return false;
	}

	for(unsigned char i = 0; i < tuple1_n_fields; ++i) {
		unsigned char tuple1_type = *tuple1.data;
		++tuple1.data;
		unsigned char tuple2_type = *tuple2.data;
		++tuple2.data;
		if((tuple1_type & ALL_TYPES) != (tuple2_type & ALL_TYPES)) {
			return false;
		}
		if(!(tuple1_type & EMPTY) && !(tuple2_type & EMPTY)) {
			switch(tuple1_type) {
			case INT:
				if(*(int*)tuple1.data != *(int*)tuple2.data) {
					return false;
				}
				break;
			case FLOAT:
				if(*(float*)tuple1.data != *(float*)tuple2.data) {
					return false;
				}
				break;
			case BOOL:
				if(*(bool*)tuple1.data != *(bool*)tuple2.data) {
					return false;
				}
				break;
			}
		}
		if(!(tuple1_type & EMPTY)) {
			switch(tuple1_type) {
			case INT:
				tuple1.data += sizeof(int);
				break;
			case FLOAT:
				tuple1.data += sizeof(float);
				break;
			case BOOL:
				tuple1.data += sizeof(bool);
				break;
			}
		}
		if(!(tuple2_type & EMPTY)) {
			switch(tuple2_type) {
			case INT:
				tuple2.data += sizeof(int);
				break;
			case FLOAT:
				tuple2.data += sizeof(float);
				break;
			case BOOL:
				tuple2.data += sizeof(bool);
				break;
			}
		}
	}
	return true;
}

void add_tuple(TupleContainer** tuples, Buffer tuple) {
	if(*tuples == NULL) {
		*tuples = (TupleContainer*)malloc(sizeof(TupleContainer));
		(*tuples)->tuple = tuple;
		(*tuples)->next = NULL;
		return;
	}
	TupleContainer* current = *tuples;
	while(current->next != NULL) {
		current = current->next;
	}
	current->next = (TupleContainer*)malloc(sizeof(TupleContainer));
	current->next->tuple = tuple;
	current->next->next = NULL;
}

Buffer get_tuple(TupleContainer* tuples, Buffer tuple) {
	TupleContainer* current = tuples;
	while(current != NULL) {
		if(tuples_match(tuple, current->tuple)) {
			return current->tuple;
		}
		current = current->next;
	}
	tuple.data = NULL;
	tuple.len = 0;
	return tuple;
}

Buffer pop_tuple(TupleContainer** tuples, Buffer tuple) {
	TupleContainer** current = tuples;
	while(*current != NULL) {
		if(tuples_match(tuple, (*current)->tuple)) {
			TupleContainer copy = **current;
			free(*current);
			*current = copy.next;
			return copy.tuple;
		}
		current = &(*current)->next;
	}
	tuple.data = NULL;
	tuple.len = 0;
	return tuple;
}

void create_socket() {
	struct sockaddr_in address;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0) {
		perror("socket creation failed");
		exit(EXIT_FAILURE);
	}

	memset(&address, 0, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(SERVER_PORT);
	address.sin_addr.s_addr = INADDR_ANY;

	if (bind(sockfd, (const struct sockaddr *)&address, sizeof(address)) < 0) {
		perror("bind failed");
		exit(EXIT_FAILURE);
	}
}

Buffer receive_request() {
	fd_set read_fds;
	FD_ZERO(&read_fds);
	FD_SET(sockfd, &read_fds);
	struct timeval timeout;
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	Buffer request;
	request.data = NULL;
	request.len = 0;
	if(select(sockfd + 1, &read_fds, NULL, NULL, &timeout) > 0) {
		if FD_ISSET(sockfd, &read_fds) {
			socklen_t addr_len = sizeof(struct sockaddr);
			int n = recvfrom(sockfd, receiveBuffer, 1024, 0, (struct sockaddr*)&client_addr, &addr_len);
			request.len = *(int*)receiveBuffer;
			request.data = (unsigned char*)malloc(request.len);
			memcpy(request.data, receiveBuffer+4, request.len);
			puts("Received request\n");
		}
	}
	return request;
}

void send_reply(unsigned char* data, int len) {
	socklen_t addr_len = sizeof(struct sockaddr);
	sendto(sockfd, data, len, 0, (struct sockaddr*)&client_addr, addr_len);
}

Buffer tuple_from_request(Buffer request) {
	Buffer tuple;
	tuple.len = request.len - 1;
	tuple.data = malloc(tuple.len);
	memcpy(tuple.data, request.data+1, tuple.len);
	return tuple;
}

int main(int argc, char const *argv[])
{
	TupleContainer* tuples = NULL;
	create_socket();
	while(true) {
		Buffer request = receive_request();
		if(request.data!=NULL) {
			if(request.data[0] == REQ_GET) {
				Buffer tuple = get_tuple(tuples, tuple_from_request(request));
				int len = 0;
				if(tuple.data == NULL) {
					puts("Nie znaleziono krotki\n");
					send_reply((unsigned char*)&len, sizeof(len));
				}
				else {
					len = tuple.len + sizeof(int);
					unsigned char* data = malloc(len);
					memcpy(data + sizeof(int), tuple.data, tuple.len);
					*(int*)data = len;
					send_reply(data, len);
					free(data);
				}
			}
			else if(request.data[0] == REQ_POP) {
				Buffer tuple = pop_tuple(&tuples, tuple_from_request(request));
				int len = 0;
				if(tuple.data == NULL) {
					puts("Nie znaleziono krotki\n");
					send_reply((unsigned char*)&len, sizeof(len));
				}
				else {
					len = tuple.len + sizeof(int);
					unsigned char* data = malloc(len);
					memcpy(data + sizeof(int), tuple.data, tuple.len);
					*(int*)data = len;
					send_reply(data, len);
					free(data);
				}
			}
			else if(request.data[0] == REQ_PUT) {
				Buffer tuple = tuple_from_request(request);
				add_tuple(&tuples, tuple);
			}
		}
		free(request.data);
	}
	return 0;
}