/*
 * A small utility for socket  programming
 *
 * Modifed from Beej socket programming tutorial
 * by Hoang Nguyen
 *
 * Feel free to modify and copy the code
 * and use it AT YOUR OWN RISK
*/

#ifndef MSG_H
#define MSG_H

#define MAX_MSG_SIZE 512
#define BACKLOG 10

typedef struct{
	char data[MAX_MSG_SIZE];
	int len;
} msg_t;

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void sendMessage(int sockfd, const msg_t* msg) {
	send(sockfd, msg->data, msg->len, 0);
}

void recvMessage(int sockfd, msg_t* msg) {
	msg->len = recv(sockfd, msg->data, MAX_MSG_SIZE - 1, 0); 
}


void sendRequest(int sockfd, const msg_t* request, msg_t* reply) {
	sendMessage(sockfd, request);
	recvMessage(sockfd, reply);
}

int connectToServer(char* serverName, char* port) {
	struct addrinfo hints, *serverinfo, *p; 
	int ret;
	int sockfd;
	char s[INET6_ADDRSTRLEN];

	int numbytes;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;


	// get the information
	ret = getaddrinfo(serverName, port, &hints, &serverinfo);
	
	if (ret != 0) {
		printf("\tgetaddrinfo error: %s\n", gai_strerror(ret));
		return -1;
	}

	for (p = serverinfo; p != NULL; p = p->ai_next) {
		sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
		if (sockfd == -1) { 
			perror("client: socket");
			continue;
		}

		if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			perror("client: socket");
			close(sockfd);
			continue;
		} 
		break;	
	}
	if (p == NULL) {
		printf("cannot connect to the server\n");
		return -1;
	}
	
	freeaddrinfo(serverinfo);

	inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof(s));
	printf("\tConnected to %s:%s\n", s, port);

	return sockfd;
}

void startServer(char* port, void *(*servefunc)(int) ) {
	int sockfd, newfd;
	int yes = 1;
	struct addrinfo hints, *serverinfo, *p;	

	struct sockaddr_storage their_addr; // connector's address information

	char s[INET6_ADDRSTRLEN];

	socklen_t sin_size;

	int ret;

	memset(&hints, 0, sizeof(hints));

	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	hints.ai_flags = AI_PASSIVE;

	ret = getaddrinfo(NULL, port, &hints, &serverinfo);
	
	if (ret == -1) {
		printf("\t getaddrinfo: %s\n", gai_strerror(ret));
		return;
	}

 	for (p = serverinfo; p != NULL; p = p->ai_next) {
		sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
		if (sockfd == -1) { 
			perror("server: socket");
			continue;
		}

		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
			perror("setsockopt");
			return;
		}

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			perror("sever: bind");
			close(sockfd);
			continue;
		} 
		break;	
	}

	if (p == NULL) {
		printf("cannot bind \n");
		return;
	}

	printf("\t Binded to port %s\n", port);
	freeaddrinfo(serverinfo);

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		return;
	}

	printf("Waiting for incoming connections...\n");

	while (1) {
		sin_size = sizeof(their_addr);
		newfd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
		
		if (newfd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family, get_in_addr( (struct sockaddr *) &their_addr), s, sizeof(s));
		
		printf("\tGot connection from %s\n", s);		

		servefunc(newfd);
	}

}

#endif