#include "ConnectionFactory.h"

string * attemptHeader(BufferedFile & file);
BufferedFile * acceptAndGetFile(int fdin);
int server(int port); 

ConnectionFactory::ConnectionFactory(int start) {
	init(start,start+1,start+2,start+3);
}
ConnectionFactory::ConnectionFactory(int inputPort,int outputPort,int filterPort,int configPort) {
	init(inputPort,outputPort,filterPort,configPort);
}
bool ConnectionFactory::isThereANewConnection() {
	tv.tv_sec = 0;
	tv.tv_usec = 500; //.5 milliseconds
	conn_set = conn;
	int currCount = select(maxfd+1, &conn_set, NULL, NULL, &tv);
	return (currCount > 0);
}

#define MAXHEADERSIZE 4096

string * attemptHeader(BufferedFile & file) {
	char a[MAXHEADERSIZE+1];
	char * data = NULL;
	string work;
	string hStart = "<header>";
	string hClose = "</header>";
	int oldWait = file.getWait();
	string * buff = NULL;
	file.setWait(10000); //10 milliseconds
	if (file.readReady()) {
			int count = file.Read((char*)a,MAXHEADERSIZE);
			if (count > 0) {
				a[count] = '\0'; //Make a legal string
				work = string(a);
				int headerStart = work.find("<header>",0);
				int headerEnd = work.find("</header>",0);
				if (headerStart!= string::npos && headerEnd != string::npos && headerStart < headerEnd && headerEnd) {
					buff = new string(work.substr(headerStart,headerEnd + hClose.size()));
					headerEnd = headerEnd+hClose.size();
				} else {
					headerStart = headerEnd = 0;
				}
				if (headerEnd < count) {
					data = new char[count-headerEnd];
					memcpy(data,(char*)a+(headerEnd),count-headerEnd);
					file.pushBack(data,count-headerEnd);
				}
			}
	}
	file.setWait(oldWait);
	return buff;
}
BufferedFile * acceptAndGetFile(int fdin) {
	struct sockaddr_in clientname;
	int size = sizeof (struct sockaddr);
	int fd = accept(fdin, (struct sockaddr *) &clientname, &(socklen_t)size);
	return new BufferedFile(fd);
}
int max(int a, int b) {
	if (a > b) {
		return a;
	}
	return b;
}

	//<header>
	//<name>Sample Program</name>
	//<interleaved>1</interleaved>
	//<rate>44100</rate>
	//<channels>2</channels>
	//<endian>little</endian>
	//<type>short</type>
	//<framesize>4<framesize>
	//</header>

//Connection<T>  *	ConnectionFactory::processConnection(
ConnectionWrap * ConnectionFactory::processConnection()  { //MUST HAVE CALL isThereANewConnection() before!
	BufferedFile * file = NULL;
	string * header = NULL;
	Connection<char> * charConn = NULL;
	Connection<short> * shortConn = NULL;
	Connection<float> * floatConn = NULL;
	Connection<double> * doubleConn = NULL;
	if (FD_ISSET(configfd,&conn_set)) {
			file = acceptAndGetFile(configfd);
			ConfigConnection * config = new ConfigConnection(*file);
			ConnectionWrap * wrap = new ConnectionWrap(config,CHARCONN);
			return wrap;
	}
	bool input,output,filter;
	input =	output = filter = false;
	if (FD_ISSET(inputfd,&conn_set)) {
			file = acceptAndGetFile(inputfd);
	} else if (FD_ISSET(outputfd,&conn_set)) {
			file = acceptAndGetFile(outputfd);
	} else if (FD_ISSET(filterfd,&conn_set)) {
			file = acceptAndGetFile(filterfd);
	} else {
		return NULL;
	}
	header = attemptHeader(*file);
	string name = "";
	string type = "short";
	try {
		if (header!=NULL) {
			Message m = Message(*header);
			TreeNode * node = m.getTree();
			if (node->existChild("header")) {
				node = node->getChild("header");
				if (node->existChild("name")) {
					name = node->getChild("name")->getValue();
				}
				if (node->existChild("type")) {
					type = node->getChild("type")->getValue();
				}
			}
		}
	} catch (string * err) {
	}

	//Is there a better way?
	//Connection<T> * out = NULL;
	ConnectionWrap  * wrap;
	if (type == "short") {
		if (input) 	 { (shortConn) =  (Connection<short>*)(new InputConnection<short>((*file),name));}
		else if (output) { (shortConn) =  (Connection<short>*)(new OutputConnection<short>((*file),name));}
		else if (filter) { (shortConn) =  (Connection<short>*)(new FilterConnection<short>((*file),name));}
		(shortConn)->setType("short");
		wrap = new ConnectionWrap((void*)shortConn,SHORTCONN);
	} else if (type == "byte") {
		if (input) 	 { (charConn) =  (Connection<char>*)(new InputConnection<short>((*file),name));}
		else if (output) { (charConn) =  (Connection<char>*)(new OutputConnection<short>((*file),name));}
		else if (filter) { (charConn) =  (Connection<char>*)(new FilterConnection<short>((*file),name));}
		(charConn)->setType("byte");
		wrap = new ConnectionWrap((void*)charConn,CHARCONN);
	} else if (type == "float") {
		if (input) 	 { (floatConn) =  (Connection<float>*)new InputConnection<short>((*file),name);}
		else if (output) { (floatConn) =  (Connection<float>*)new OutputConnection<short>((*file),name);}
		else if (filter) { (floatConn) =  (Connection<float>*)new FilterConnection<short>((*file),name);}
		(floatConn)->setType("float");
		wrap = new ConnectionWrap((void*)floatConn,FLOATCONN);
	} else if (type == "double") {
		if (input) 	 { (doubleConn) = (Connection<double>*) new InputConnection<short>((*file),name);}
		else if (output) { (doubleConn) = (Connection<double>*) new OutputConnection<short>((*file),name);}
		else if (filter) { (doubleConn) = (Connection<double>*) new FilterConnection<short>((*file),name);}
		(doubleConn)->setType("double");
		wrap = new ConnectionWrap((void*)doubleConn,DOUBLECONN);
	}
	return wrap;
}//Changed from interface

void ConnectionFactory::init(int inputPort,int outputPort,int filterPort,int configPort) {
	inputfd  = server(inputPort);
	outputfd = server(outputPort);
	filterfd = server(filterPort);
	configfd = server(configPort);
	FD_ZERO (&conn);
	FD_ZERO (&conn_set);
	FD_SET (inputPort, &conn);
	FD_SET (outputPort, &conn);
	FD_SET (filterPort, &conn);
	FD_SET (configPort, &conn);
	maxfd = max(inputfd,max(outputfd,max(filterfd,configfd)));
}

int server(int port) {
	int serverfd;
	struct sockaddr_in serveraddr;
	serverfd = socket(AF_INET,SOCK_STREAM,0);
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(serverfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr));
	listen(serverfd,30);
	fprintf(stderr,"Waiting for connection on port %d\n",port);
	return serverfd;
}

