//
//sockserv.c
//
//Sock server. Lists, polls and maintains all sockets.
//
//
//-UserX 2001/11/11


#include <stdlib.h>
#ifdef __FreeBSD__
#include <string.h>
#endif
#include "net/sockserv.h"
#include "base/array.h"
#include "base/dblock.h"
//#include "core.h"
#include "net/noderef.h"
#include "net/nodeserv.h"
//#include "protocol.h"
#include "net/protocfg.h"

#include "base/logger.h"
#include "base/str.h"

#include "misc/global.h"

#include "ui/ui.h"

//typedef struct sd_set {
//	int size;
//	SOCKET socks[0];
//}

DataBlock *sdRead = NULL;
DataBlock *sdWrite = NULL;

#define SDREAD ((fd_set *)sdRead->data)
#define SDWRITE ((fd_set *)sdWrite->data)

SockArrayHandle *SocksListening = NULL;
SockArrayHandle *SocksOpen = NULL;

void (*addinsock)(SockHandle *, int *) = NULL;


SockHandle *blanksockhandleptr = NULL;

void sockservInit(void (*addfunc)(SockHandle *, int *)) {
	//todo: verify functions passed to arrayMake 
	if(SocksListening == NULL) {
		SocksListening = (SockArrayHandle *) arrayMake(sizeof(SockHandle *), 0, &blanksockhandleptr, (ArrayFuncCreate) sockMakeAt, (ArrayFuncDelete) sockFree);
	}
	if(SocksOpen == NULL) {
		SocksOpen = (SockArrayHandle *) arrayMake(sizeof(SockHandle *), 0, &blanksockhandleptr, (ArrayFuncCreate) sockMakeAt, (ArrayFuncDelete) sockFree);
	}
	if(addfunc == NULL) {
		LOGERROR("sockservInit: NULL passed for addfunc.");
		abort();
	}
	addinsock = addfunc;
}

//
void sockservPoll(int shortdelay){
#ifdef _WINDOZE_
	struct timeval tv = {0, 200000};  //should increase responsiveness of tray control
#else
	struct timeval tv = {1, 0};
#endif
	SOCKET maxpoll = 0;
	int i;
	//a little inefficient in terms of space used but should work (hopefully) work on any platform.
	if(SocksListening == NULL || SocksOpen == NULL) {
		return;
	}
	if(nextdhdelay >= 0) {
		//drop wait time down to 1/10th of a second after dh computations
		tv.tv_sec = 0;
		tv.tv_usec = 100000;
	}
	if(shortdelay != 0) {
		tv.tv_sec = 0;
		tv.tv_usec = 1000;
	}

	sdRead = dblockResize(sdRead, sizeof(fd_set) + sizeof(SOCKET) * (SocksListening->size + SocksOpen->size));
	sdWrite = dblockResize(sdWrite, sizeof(fd_set) + sizeof(SOCKET) * SocksOpen->size);

	FD_ZERO(SDREAD);
	FD_ZERO(SDWRITE);
	for(i = 0; i < SocksOpen->size; i++) {
		if(SocksOpen->data[i] != NULL) {
			switch(SocksOpen->data[i]->status) {
			case SOCK_STATUS_OK:
			case SOCK_STATUS_OPENING:
				if(SocksOpen->data[i]->Socket != SOCKET_ERROR) {
					FD_SET(SocksOpen->data[i]->Socket, SDREAD);
					if(SocksOpen->data[i]->IOPipe.outBuffer->size != 0) {
						FD_SET(SocksOpen->data[i]->Socket, SDWRITE);
					}
					if(SocksOpen->data[i]->Socket > maxpoll) {
						maxpoll = SocksOpen->data[i]->Socket;
					}
				}
				break;
			}
		}
	}
	for(i = 0; i < SocksListening->size; i++) {
		if(SocksListening->data[i] != NULL) {
			switch(SocksListening->data[i]->status) {
			case SOCK_STATUS_LISTENING:
				if(SocksListening->data[i]->Socket != SOCKET_ERROR) {
					FD_SET(SocksListening->data[i]->Socket, SDREAD);
					if(SocksListening->data[i]->Socket > maxpoll) {
						maxpoll = SocksListening->data[i]->Socket;
					}
				}
				break;
			}
		}
	}
	maxpoll++;
	select(maxpoll, ((fd_set *)&sdRead->data), ((fd_set *)&sdWrite->data), NULL, &tv);
	
}


void sockservAddSock(SockArrayHandle *sah, SockHandle *sh) {
	int i;
	for(i = 0; i < sah->size; i++) {
		if(sah->data[i] == NULL) {
			sah->data[i] = sh;
			return;
		}
	}
	arrayAddElements((ArrayHandle *)sah, 1);
	sah->data[sah->size - 1] = sh;
}

void sockservAddOpen(SockHandle *sh) {
	sockservAddSock(SocksOpen, sh);
}

void sockservAddListening(SockHandle *sh) {
	sockservAddSock(SocksListening, sh);
}

void sockservProcessOpen(void) {
	int i;
	for(i = 0; i < SocksOpen->size; i ++) {
		if(SocksOpen->data[i] != NULL) {
			if(SocksOpen->data[i]->Socket != SOCKET_ERROR) {
				if(FD_ISSET(SocksOpen->data[i]->Socket, SDREAD)) {
					sockRead(SocksOpen->data[i]);
				}
				if(FD_ISSET(SocksOpen->data[i]->Socket, SDWRITE)) {
					sockWrite(SocksOpen->data[i]);
				}
			}
			switch(SocksOpen->data[i]->status) {
			case SOCK_STATUS_CLOSING:
			case SOCK_STATUS_CLOSED:
				LOGDEBUG (stringJoin("sockservProcessOpen:Close:", ptrToString(SocksOpen->data[i])));
				sockClose(SocksOpen->data[i]);
				sockFree(SocksOpen->data[i]);
				SocksOpen->data[i] = NULL;
				break;
			}
		}
	}
}

void sockservProcessListening(void) {
	SockHandle *sh;
	int i;
	int result;
	for(i = 0; i < SocksListening->size; i ++) {
		if(SocksListening->data[i] != NULL) {
			if(FD_ISSET(SocksListening->data[i]->Socket, SDREAD)) {
				sh = sockAccept(SocksListening->data[i]);
				if(sh != NULL) {
					addinsock(sh, &result);
					if(result == 0) {
						sockservAddOpen(sh);
					}
				}
			}
		}
	}
}

void sockservProcess(int shortdelay) {
	if(SocksListening == NULL || SocksOpen == NULL) {
		return;
	}
	sockservPoll(shortdelay);
	sockservProcessOpen();
	sockservProcessListening();
}

NodeRef *sockservPickNode(void) {
	NodeRef *nr = nodeservPickNode();
	int i;
	for(i = 0; i < nodeservTotalNodeRefs(); i++, nr = nodeservPickNextNode(nr)) {
		if(ignoreself == 0 || stringCaseCompare(nr->HostName, hostname) != 0) {
			if(protocolFilter(nr->Protocol) != 0) {
				return nr;
			}
		}
	}
	noderefFree(nr);
	return NULL;
}

//connect to random node
//todo: change when noderef info improves
SockHandle *sockservConnect(void) {
	SockHandle *sh;
	NodeRef *nr = NULL;
//	int i;
	int er;

	nr = sockservPickNode();
	if(nr == NULL) {
		return NULL;
	}

	sh = sockMake();

//trying a new node on a failed connection is actually a bad idea.
//If the IRC server goes down it will keep looping around nodes until 
//they crash/choke.
//Once the proper network is up (that is no IRC server) trying again 
//will be handled elsewhere.
//	for(i = 0; i < totalNodes(); i++, nr = pickNextNode(nr)) {
//	for(i = 0; i <= retries; i++, noderefFree(nr), nr = sockservPickNode()) {
		sockInit(sh);
		er = sockSetNodeRef(sh, nr);
		if(er == SOCK_ERR_NONE) {
			er = sockOpen(sh);
//			if(er == SOCK_ERR_NONE) {
//				break;
//			}
		}
//	}
	noderefFree(nr);
	if(er != SOCK_ERR_NONE) {
		sockFree(sh);
		return NULL;
	}

	sockservAddOpen(sh);

	return sh;
}

SockHandle *sockservListen(NodeRef *nr) {
	SockHandle *sh;
	int er;

	if(nr->PortNum == 0) {
		return NULL;
	}

	sh = sockMake();

	if(sh == NULL) {
		return(NULL);
	}

	er = sockSetLocalNodeRef(sh, nr);

	if(er != SOCK_ERR_NONE) {
		sockFree(sh);
		return NULL;
	}

	er = sockListen(sh);

	if(er != SOCK_ERR_NONE) {
		sockClose(sh);
		sockFree(sh);
		return NULL;
	}

	sockservAddListening(sh);

	return sh;
}

int sockservStartListen(void) {
	int i;
	int failures = 0;
	//sockservInit();
	if(SocksListening == NULL) {
		return -1;
	}
	//close any open listening sockets
	for(i = 0; i < SocksListening->size; i++) {
		if(SocksListening->data[i] != NULL) {
			LOGDEBUG(stringJoinMany(
					"sockservStartListen:Close:(",
					intToString(i),
					")",
					ptrToString(SocksListening->data[i]),
				NULL));
			sockClose(SocksListening->data[i]);
			sockFree(SocksListening->data[i]);
			SocksListening->data[i] = NULL;
		}
	}
	for(i = 0; i < LNRA->size; i++) {
		LOGDEBUG(stringJoinMany(
				"sockservStartListen:Listen:(", 
				intToString(i),
				")",
				ptrToString(&LNRA->data[i]),
			NULL));
		if(!sockservListen(&LNRA->data[i])) {
#ifdef _WINDOZE_
			char *s = stringJoinMany(_("Unable to bind listening connection to socket: "), intToString(LNRA->data[i].PortNum), NULL);
			uiDialog(_("Unable to bind socket"), s, UIDIALOG_OK, UIDIALOG_OK);
			stringFree(s);
#endif
			failures++;
		}
	}
	return failures;
}
