/*
 * NetMessage.cpp: implementation for sending BMessages across the net
 * 
 * This is unsupported demo code. Use at your own risk.
 */
#include "NetMessage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <socket.h>
#include <netdb.h>
#include <OS.h>
#include <math.h>

static const char TRANSACTION_ID[] = "!TransactionID!";

NetClient::NetClient(const char *hostname, int port)
{
	struct hostent *h;

	h = gethostbyname(hostname);
	if (h == NULL) {
		address = 0;
	} else {
		memcpy(&this->address, h->h_addr, sizeof(this->address));
	}
	this->port = port;
	this->tid = fmod(system_time(), (double)UINT_MAX + 1);
	this->timeout = 10;	/* 10 second total timeout */
	this->retrans = 4;	/* with 4 retransmissions, 1 packet every 2 seconds */
	this->sock = socket(AF_INET, SOCK_DGRAM, 0);
	this->replydata = (char *)malloc(B_UDP_MAX_SIZE);
}

NetClient::~NetClient(void)
{
	if (sock >= 0) {
		closesocket(sock);
		sock = -1;
		if (replydata) {
			free(replydata);
			replydata = NULL;
		}
	}
}

static int
waitpacket(
		   int s,
		   double to
		   )
{
	struct timeval tv;
	struct fd_set fds;

	tv.tv_sec = (long)to / 1000000;
	tv.tv_usec = (long)to % 1000000;
	FD_ZERO(&fds);
	FD_SET(s, &fds);
	return (select(s + 1, &fds, NULL, NULL, &tv) == 1);
}

long 
NetClient::SendMessage(
					   BMessage *request,
					   BMessage **replyp
					   )
{
	char *data;
	long size;
	struct sockaddr_in sin;
	int i;
	int trans;
	BMessage *reply;
	int res;
	double to;
	struct sockaddr_in from;
	int fromlen;
	unsigned long replytid;
	unsigned long mytid;
	double this_to;
	double then;
	double now;
	
	request->AddLong(TRANSACTION_ID, (long)(mytid = tid++));
	request->Flatten(&data, &size);
	sin.sin_family = AF_INET;
	sin.sin_port = port;
	sin.sin_addr.s_addr = address;
	memset(sin.sin_zero, 0, sizeof(sin.sin_zero));
	trans = retrans + 1;
	to = (timeout * 1000000.0) / trans;
	for (i = 0; i < trans; i++) {
		res = sendto(this->sock, data, size, 0, (struct sockaddr *)&sin, 
					 sizeof(sin));
		if (res < 0) {
			free(data);
			return (NET_CANT_SEND);
		}
		this_to = to;
		then = system_time();
		while (this_to > 0 && waitpacket(this->sock, this_to)) {
			fromlen = sizeof(from);
			res = recvfrom(this->sock, replydata, B_UDP_MAX_SIZE, 0, 
						   (struct sockaddr *)&from, &fromlen);
			if (res < 0) {
				free(data);
				return (NET_CANT_RECV);
			}
			reply = new BMessage();
			reply->Unflatten(replydata);
			if (!reply->HasLong(TRANSACTION_ID)) {
				/* bogus packet: ignore */
			} else {
				replytid = reply->FindLong(TRANSACTION_ID);
				if (replytid == mytid) {
					*replyp = reply;
					free(data);
					return (B_NO_ERROR);
				} else {
					/* bogus packet: ignore */
				}
			}
			delete reply;

			/*
			 * Update our timeout and wait again
			 * This way, a bogus packet doesn't screw up 
			 * our timeouts
			 */
			now = system_time();
			this_to -= (now - then);
			then = now;
		}
	}
	free(data);
	return (B_TIMED_OUT);
}


NetServer::NetServer(int port)
{
	struct sockaddr_in sin;
	int res;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock >= 0) {
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = 0;
		sin.sin_port = htons(port);
		memset(sin.sin_zero, 0, sizeof(sin.sin_zero));
		res = bind(sock, (struct sockaddr *)&sin, sizeof(sin));
		if (res < 0) {
			requestdata = NULL;
			return;
		}
		requestdata = (char *)malloc(B_UDP_MAX_SIZE);
		return;
	}
	requestdata = NULL;
	return;
}

NetServer::~NetServer(void)
{
	if (sock >= 0) {
		closesocket(sock);
		sock = -1;
		if (requestdata) {
			free(requestdata);
			requestdata = NULL;
		}
	}
}

long
NetServer::ReceiveMessage(BMessage **messagep)
{
	int len;
	BMessage *message;
	struct sockaddr_in from;
	int fromlen;
	
	for (;;) {
		fromlen = sizeof(from);
		len = recvfrom(sock, requestdata, B_UDP_MAX_SIZE, 0, 
					   (struct sockaddr *)&from, &fromlen);
		if (len < 0) {
			return (NET_CANT_RECV);
		}
		message = new BMessage();
		message->Unflatten(requestdata);
		*messagep = message;
		if (message->HasLong(TRANSACTION_ID)) {
			tid = message->FindLong(TRANSACTION_ID);
			address = from;
			return (B_NO_ERROR);
		}
	}
}

long
NetServer::SendReply(
					 BMessage *request
					 )
{
	char *data;
	long size;
	int res;

	request->AddLong(TRANSACTION_ID, tid);
	request->Flatten(&data, &size);
	res = sendto(this->sock, data, size, 0, (struct sockaddr *)&address,
				 sizeof(address));
	if (res < 0) {
		free(data);
		return (NET_CANT_SEND);
	}
	free(data);
	return (B_NO_ERROR);
}

