#include "ConnectionFactory.h"

/**
 * attempts to read the header from file. Will pushback any data that wasn't in the header.
 */
string * attemptHeader(BufferedFile * file);
/**
 * Start a server on port port and return the servers fd.
 */
int server(int port); 

ConnectionFactory::ConnectionFactory(BufferedFileFactory * bf,int start) {
	init(bf,start,start+1,start+2,start+3);
}
ConnectionFactory::ConnectionFactory(BufferedFileFactory * bf,int inputPort,int outputPort,int filterPort,int configPort) {
	init(bf,inputPort,outputPort,filterPort,configPort);
}
//Might want to switch to BufferedFiles
bool ConnectionFactory::isThereANewConnection() {
	//tv.tv_sec = 0;
	//tv.tv_usec = 0; //0 milliseconds
	//conn_set = conn;
	//int currCount = select(maxfd+1, &conn_set, NULL, NULL, &tv);
	//return (currCount > 0);
	int newConnections = 0;
	//cerr << "InputBF: "<< inputBF->getFileHandle() << " " << (int)(inputBF) << "\n";
	if (inputBF->readReady()) {
		newConnections++;
	}
	if (outputBF->readReady()) {
		newConnections++;
	}
	if (filterBF->readReady()) {
		newConnections++;
	}
	if (configBF->readReady()) {
		newConnections++;
	}
	return (newConnections > 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()) {
	if (file->ready(BUFFER_READ)) {
			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!= (int)string::npos && headerEnd != (int)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 * ConnectionFactory::acceptAndGetFile(int fdin) {
	struct sockaddr_in clientname;
	int size = sizeof (struct sockaddr);
	int fd = accept(fdin, (struct sockaddr *) &clientname, &(socklen_t)size);
	cerr << "FileDescriptor: " << fd << "\n";
	return bf->getNewBufferedFile(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)) {
	if (configBF->readReady()) {
			//file = acceptAndGetFile(configfd);
			file = acceptAndGetFile(configBF->getFileHandle());
			ConfigConnection * config = new ConfigConnection(file);
			ConnectionWrap * wrap = new ConnectionWrap(config,CONFIGCONN);
			return wrap;
	}
	bool input,output,filter;
	input =	output = filter = false;
	string name="";
	//if (FD_ISSET(inputfd,&conn_set)) {
	if (inputBF->readReady()) {
			file = acceptAndGetFile(inputfd);
			input = true;
			name+="input";
	//} else if (FD_ISSET(outputfd,&conn_set)) {
	} else if (outputBF->readReady()) {
			file = acceptAndGetFile(outputfd);
			output = true;
			name+="ouput";
	//} else if (FD_ISSET(filterfd,&conn_set)) {
	} else if (filterBF->readReady()) {
			file = acceptAndGetFile(filterfd);
			filter = true;
			name+="filter";
	} else {
		return NULL;
	}
	char number[12];
	snprintf(number,12,"%d",file->getFileHandle());
	name+=string(number);
	cerr << "FD: " << file->getFileHandle() << "\n";
	header = attemptHeader(file);
	string type = "short";
		if (header!=NULL) {
			Message * msg = new Message(*header);
			delete header;
			TreeNode * node = msg->getTree();
			try {
				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) {}
			delete msg;
		}

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

void ConnectionFactory::init(BufferedFileFactory * bff,int inputPort,int outputPort,int filterPort,int configPort) {
	if (inputPort) {
		inputfd  = server(inputPort);
	}
	if (outputPort) {
		outputfd = server(outputPort);
	}
	if (filterPort) {
		filterfd = server(filterPort);
	}
	if (configPort) {
		configfd = server(configPort);
	}
	//FD_ZERO (&conn);
	//FD_ZERO (&conn_set);
	//FD_SET (inputfd, &conn);
	//FD_SET (outputfd, &conn);
	//FD_SET (filterfd, &conn);
	//FD_SET (configfd, &conn);
	//maxfd = max(inputfd,max(outputfd,max(filterfd,configfd)));
	bf = bff;
	if (inputPort) {
		inputBF  = bf->getNewBufferedFile(inputfd);
		inputBF->setReadOnly();
	} else {
		inputBF  = new BufferedFile(-1);
	}
	if (outputPort) {
		outputBF = bf->getNewBufferedFile(outputfd);
		outputBF->setReadOnly();
	} else {
		outputBF = new BufferedFile(-1);
	}
	if (filterPort) {
		filterBF = bf->getNewBufferedFile(filterfd);
		filterBF->setReadOnly();
	} else {
		filterBF = new BufferedFile(-1);
	}
	if (configPort) {
		configBF = bf->getNewBufferedFile(configfd);
		configBF->setReadOnly();
	} else {
		configBF = new BufferedFile(-1);
	}
}

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;
}
ConnectionFactory::~ConnectionFactory() {
	inputBF->Close();
	outputBF->Close();
	filterBF->Close();
	configBF->Close();
	delete inputBF;
	delete outputBF;
	delete filterBF;
	delete configBF;
}
