#include "agent.h"
#include <list>
#include <string>

enum {FAIL, SUCCESS};

bool DEBUG= false;

list<Host> nodes;			// node listesi
list<Host>::iterator nit;	// iterator
int nNodes;					// sistemdeki node sayısı
WORKLOAD *workLoad;			// nodes' workload table
char cmdTable[100][32];		// command table
int nCmdTable;				// its iterator
char hName[32];

bool readWorkload(char *ip, WORKLOAD *wl);
bool getHosts();
int countNodeList();
void excludeNode(WORKLOAD *wl);
int Server();
int getWorkload(int sockfd);
void Shell();
char *makeDecision();
bool memberCmdTable(char *line);
void readCmdTable();
ssize_t writen(int fd, const void *vptr, size_t n);
ssize_t readline(int fd, void *vptr, size_t maxlen);


int main(int argc, char **argv)
{
	int pid, pidMonitor;
	char command[32];
	
	/* get nodes' name and ip-addrs.
	*/
	if (!getHosts()) exit(0);
	
	nNodes= countNodeList();	
	
	if (DEBUG) printf("nNodes= %d\n", nNodes);
	
	/* debug mode ?
	*/	
	if (argc > 1)
		if (!strncmp(argv[1], "-d", 2))
			DEBUG= true;
	
	/* run Server process
	*/
	
	if (!(pid= fork())) { 
		//child process starts here
		if (Server())
			exit(SUCCESS);
		else {
			printf("\t\tServer process couldn't make socket connection \n\
			this node won't answer the other nodes' requests ...\n");
			exit(FAIL);
		}
	} else if (pid == -1) {
		printf("Server process failed ...\n");
		exit(FAIL);
	}
	// parent process goes on from here

	/* MONITORING...
	
	if (!(pidMonitor= fork())) { 
		//child process starts here
		system("/usr/bin/gtop");
	} else if (pidMonitor == -1) {
		printf("Monitor process failed ...\n");
		exit(FAIL);
	}
	*/
		
	/* accept user requests and evaluate them
	*/
	Shell();
	
	/* kill server process and go out
	*/
	sprintf(command, "kill -9 %d", pid);
	system(command);
	
	/*
	sprintf(command, "kill -9 %d", pidMonitor);
	system(command);
	*/
			
	return 1;
}

bool getHosts()
{
	FILE *fhosts, *fHN;
	char dummy[128];
	string line, c[3];
	unsigned int i, j, k;
	
	if ((fHN= fopen("/etc/HOSTNAME", "r")) == NULL) {
		puts("I cannot read /etc/HOSTNAME ...\n");
		return false;
	}
	
	/* hostname i oku
	*/
	fscanf(fHN, "%s",hName);
	
	fclose(fHN);
	//printf("-%s-\n", hName);
	
	/* /etc/hosts dosyasını oku ve parse et
	*/
	if ((fhosts= fopen("/etc/hosts", "r")) == NULL) {
		puts("I cannot read /etc/hosts ...\n");
		return false;
	} 
	
	for (i= 0; !feof(fhosts); i++) {
		
		bzero(dummy, sizeof dummy);
		fgets(dummy, sizeof dummy, fhosts);
		line= dummy;
		
		if (dummy[0] == 0) continue;	// boş satır ise atla
		//printf("dummy= %s\n", dummy);
		
		// comment line ise atla
		if (line.substr(0, 1) == "#") continue;
			
		c[0]= c[1] = c[2] = "";
		
		for (j=0, k=0; j< line.length()-1; j++) {
			
			if (line.substr(j, 1) == " " || ((line.substr(j, 1)).c_str())[0] == '\t') {
				// skip white space and tab char.
				for ( ; line.substr(j, 1) == " " || ((line.substr(j, 1)).c_str())[0] == '\t' ; j++);
				
				k++;
			} 
			c[k]+= line.substr(j, 1);
		}
		
		if (c[0].substr(0, 3) == "127") // loop back çevrimi ise atla
			continue;
		else if (c[0].empty())
			continue;
		else if (c[1].empty() && c[2].empty()) {
			printf("Bir node'un (%s)ip adresi var fakat ismi yok, \n \
					lütfen /etc/hosts dosyasindan bunu düzeltin ...\n", c[0].c_str());
			return false;
		}
		else if (c[2].empty()) {
			printf("Lütfen host adresleri için alias alanlarini da doldurunuz ...\n");
			return false; 
		}
		
		if (hName == c[2])
			nodes.push_front(Host(c[2].c_str(), c[0].c_str()));
		else 
			nodes.push_back(Host(c[2].c_str(), c[0].c_str()));
	}
	fclose(fhosts);
	
	return true;
}

bool readWorkload(char *ip, WORKLOAD *wl)
{
	string sendCmd, recvStr, c[10];
	char recvline[MAXLINE];
	int sockfd, check;
	unsigned int i, j, k;
	struct sockaddr_in servaddr;
	
	/* make connection
	*/
	sockfd= socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		printf("WARNING: I cannot create socket ...\n");
		excludeNode(wl);
		return false;
	}	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family= AF_INET;
	servaddr.sin_port= htons(SERV_PORT);
	inet_pton(AF_INET, ip, &servaddr.sin_addr);
	check= connect(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr));
	if (check == -1)  {
		printf("WARNING: connection failed ...\n");
		excludeNode(wl);
		return false;
	}
	
	/* send command for getting workload 	
	*/
	sendCmd= "?";		
	writen(sockfd, sendCmd.c_str(), sendCmd.length());
	
	/* read node's answer
	*/
	if (readline(sockfd, recvline, MAXLINE) == 0) {
		puts("WARNING : server terminated prematurely");
		return false;
	}

	/* parse the answer
	*/
	recvStr= recvline;
	for (i= 0; i< 10; i++) c[i]= "";
	
	for (j=0, k=0; j< recvStr.length(); j++) {

		if (recvStr.substr(j, 1) == ",") {
			k++;
			continue;
		} 
		c[k]+= recvStr.substr(j, 1);
	}
	
	wl->no_of_process		= atoi(c[3].c_str());
	wl->cpu_usage.available	= atof(c[4].c_str());
	wl->cpu_usage.used		= atof(c[5].c_str());
	wl->cpu_usage.average	= atof(c[0].c_str());
	wl->mem_usage.available	= atof(c[6].c_str());
	wl->mem_usage.used		= atof(c[7].c_str());
	wl->mem_usage.average	= atof(c[1].c_str());
	wl->swp_usage.available	= atof(c[8].c_str());
	wl->swp_usage.used		= atof(c[9].c_str());
	wl->swp_usage.average	= atof(c[2].c_str());
	
	if (DEBUG)
	printf("\
	no_of_process	   =%d \n\
	cpu_usage.available=%f \n\
	cpu_usage.used	   =%f \n\
	cpu_usage.average  =%f \n\
	mem_usage.available=%f \n\
	mem_usage.used	   =%f \n\
	mem_usage.average  =%f \n\
	swp_usage.available=%f \n\
	swp_usage.used	   =%f \n\
	swp_usage.average  =%f \n", wl->no_of_process,
								wl->cpu_usage.available,
								wl->cpu_usage.used,
	 							wl->cpu_usage.average,
								wl->mem_usage.available,
								wl->mem_usage.used,
								wl->mem_usage.average,
								wl->swp_usage.available,
								wl->swp_usage.used,
								wl->swp_usage.average);
								    	
	/* close socket connection 
	*/
	close(sockfd);
	
	/* calculate delay
	*/
    
	FILE *fp;
	char line[128], command[80];
	string strLine; 
	
	sprintf(command, "ping -c 4 %s 2>/dev/null", ip);
    fp = popen(command, "r");
    if (fp== (FILE *)0) {
		printf("ERROR : impossible to run \"%s\"\n", command);
		return 0;
	}

    /* read first line 
	*/
    if ((char *)0 == fgets(line, sizeof line, fp)) {
       pclose(fp);
	   puts("ERROR : unexpected file format\n");
       return 0;
    }
	

	/* read file until last line
	*/
	while (!feof(fp)) {
		fgets(line, sizeof line, fp);
	}
		
	/* read last line
	*/
	strLine= line;
	char token[]= "round-trip min/avg/max =";
	for (i= 0; i< strLine.length(); i++) {
		if (strLine.substr(i, strlen(token)) == token)
			break;
	}
	
	i+= strlen(token);
	c[0]= c[1] = c[2] = "";
	for (j= i, k=0; j< strLine.length()-1; j++) { // -1 for exclution of "\n" char.
		
		if (strLine.substr(j, 1) == "/") {
				k++;
				continue;
		} 
		c[k]+= strLine.substr(j, 1);
	}
		
	/* convert to double
	*/
	wl->delay= atof(c[0].c_str()); 			
	if (DEBUG) printf("delay = %f\n", wl->delay);
		
	return true;
}

int Server() 
{
	int 	listenfd, connfd, tmp;	socklen_t clilen;
	struct sockaddr_in cliaddr, servaddr;
	
	listenfd= socket(AF_INET, SOCK_STREAM, 0);
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family= AF_INET;
	servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
	servaddr.sin_port= htons(SERV_PORT);
	
	tmp= bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	if (tmp	== -1) return FAIL;
		
	tmp=listen(listenfd, LISTENQ);
	if (tmp == -1) return FAIL;
	
	if (DEBUG) printf("\t... server process is ready ...\n");		
	
	for(EVER) {
		
		clilen= sizeof(cliaddr);
		connfd= accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
		getWorkload(connfd);
		close(connfd);
	}
	return SUCCESS;
}

int getWorkload(int sockfd)
{
    char line[128], command[80];
	string c[5], strLine, backStr; 
	unsigned int i, j, k;
    FILE *fp;
    WORKLOAD workLoad;

	/* get workload information via "top"
	*/
    sprintf(command, "top -n 1 2>/dev/null");
    fp = popen(command, "r");
    if (fp== (FILE *)0) {
		puts("ERROR 1: impossible to run command top\n");
		return 0;
	}

    /* read first line 
	*/
    if ((char *)0 == fgets(line, sizeof line, fp)) {
       pclose(fp);
	   puts("ERROR 2: unexpected file format\n");
       return 0;
    }
	
	/* birinci satırdaki averaj değerlerini parse et
	*/
	strLine= line;
	char token[]= "load average:";
	for (i= 0; i< strLine.length(); i++) {
		if (strLine.substr(i, strlen(token)) == token)
			break;
	}
	
	i+= strlen(token);
	c[0]= c[1] = c[2] = "";
	for (j= i, k=0; j< strLine.length()-1; j++) { // -1 for exclution of "\n" char.
		
		if (strLine.substr(j, 1) == ",") {
				k++;
				continue;
		} 
		c[k]+= strLine.substr(j, 1);
	}
		
	/* convert to double
	*/
	workLoad.cpu_usage.average= atof(c[0].c_str()); 			
	workLoad.mem_usage.average= atof(c[1].c_str()); 			
	workLoad.swp_usage.average= atof(c[2].c_str()); 			

	/* dönecek string e ekle
	*/  
	backStr= c[0]+","+c[1]+","+c[2]+","; 
	
	if (DEBUG) {
		printf("cpu_usage.average:%f, mem_usage.average:%f, swp_usage.average:%f\n",
				workLoad.cpu_usage.average, workLoad.mem_usage.average, workLoad.swp_usage.average);
	}
	
	/* İkinci satırdan process sayısını oku
	*/
	fgets(line, sizeof line, fp);
	strLine= line;
	c[0] = "";
	for (i=0; i< strLine.length(); i++) {
		if (strLine.substr(i, 1) == "p") // "p" process sayısından sanraki ilk harf.
			break;
		c[0]+= strLine.substr(i, 1);
	}
	/* convert to int
	*/
	workLoad.no_of_process= atoi(c[0].c_str());

	/* dönecek string e ekle
	*/  
	backStr+= c[0]+","; 
	
	if (DEBUG) {
		printf("no_of_process:%d\n", workLoad.no_of_process);
	}
	
	/* Üçüncü satırdan CPU kullanımını oku
	*/
	fgets(line, sizeof line, fp);
	strLine= line;
	strcpy(token, "CPU states:");
	bool okToRead= true;
	c[0]= c[1]= c[2]= c[3]= "";
	for (i= strlen(token), k= 0; i< strLine.length(); i++) {
		// "%" ile "," arasını okuma
		if (strLine.substr(i, 1) == ","){
			k++;
			okToRead= true;
			continue;
		}
		else if (strLine.substr(i, 1) == "%") {
			okToRead= false;
			continue;
		}
		if (okToRead)
			c[k]+= strLine.substr(i, 1);
			
	}

	workLoad.cpu_usage.available= atof(c[3].c_str()); 			
	workLoad.cpu_usage.used     = atof(c[0].c_str())+atof(c[1].c_str())+
								  atof(c[2].c_str());

	/* dönecek string e ekle
	*/  
	char tmp[32];
	sprintf(tmp, "%f", workLoad.cpu_usage.used);
	backStr+= c[3]+ "," + tmp + ","; 
	
	if (DEBUG) {
		printf("cpu_usage.available:%f, cpu_usage.used:%f\n",
				 workLoad.cpu_usage.available, workLoad.cpu_usage.used);
	}
	
	/* Dördüncü satırdan MEMORY kullanımını oku
	*/
	fgets(line, sizeof line, fp);
	strLine= line;
	strcpy(token, "Mem:");
	okToRead= true;
	c[0]= c[1]= c[2]= c[3]= c[4]= "";
	for (i= strlen(token), k= 0; i< strLine.length(); i++) {
		// "K" ile "," arasını okuma
		if (strLine.substr(i, 1) == ","){
			k++;
			okToRead= true;
			continue;
		}
		else if (strLine.substr(i, 1) == "K") {
			okToRead= false;
			continue;
		}
		if (okToRead)
			c[k]+= strLine.substr(i, 1);
			
	}

	workLoad.mem_usage.available= atof(c[2].c_str()); 			
	workLoad.mem_usage.used     = atof(c[1].c_str());
	
	/* dönecek string e ekle
	*/  
	backStr+= c[2]+","+c[1]+","; 

	
	if (DEBUG) {
		printf("mem_usage.available:%f, mem_usage.used:%f\n", 
				workLoad.mem_usage.available, workLoad.mem_usage.used);
	}

	/* Beşinci satırdan SWAP SPACE kullanımını oku
	*/
	fgets(line, sizeof line, fp);
	strLine= line;
	strcpy(token, "Swap:");
	okToRead= true;
	c[0]= c[1]= c[2]= c[3]= c[4]= "";
	for (i= strlen(token), k= 0; i< strLine.length(); i++) {
		// "K" ile "," arasını okuma
		if (strLine.substr(i, 1) == ","){
			k++;
			okToRead= true;
			continue;
		}
		else if (strLine.substr(i, 1) == "K") {
			okToRead= false;
			continue;
		}
		if (okToRead)
			c[k]+= strLine.substr(i, 1);
			
	}

	workLoad.swp_usage.available= atof(c[2].c_str()); 			
	workLoad.swp_usage.used     = atof(c[1].c_str());
	
	/* dönecek string e ekle
	*/  
	backStr+= c[2]+","+c[1]; 
	
	if (DEBUG) {
		printf("swp_usage.available:%f, swp_usage.used:%f\n", 
				workLoad.swp_usage.available, workLoad.swp_usage.used);
	}	
    pclose(fp);
	
	writen(sockfd, backStr.c_str(), backStr.length());
   
   	return 1;
}

int countNodeList()
{
	int i;

	nit= nodes.begin();
	for (i= 0; nit!= nodes.end(); nit++, i++);
	
	return i;
}

void excludeNode(WORKLOAD *wl)
{
	wl->no_of_process		= -1;
	wl->cpu_usage.available	= -1.0;
	wl->cpu_usage.used		= -1.0;
	wl->cpu_usage.average	= -1.0;
	wl->mem_usage.available	= -1.0;
	wl->mem_usage.used		= -1.0;
	wl->mem_usage.average	= -1.0;
	wl->swp_usage.available	= -1.0;
	wl->swp_usage.used		= -1.0;
	wl->swp_usage.average	= -1.0;
}

void Shell()
{
	char line[128], command[32], *nodeName;
	int i;
		
	readCmdTable();
	
	for(EVER) {
		
		fprintf(stdout, "[%s]$ ", hName);
		fgets(line, sizeof line, stdin);
		line[strlen(line)-1]= '\0';
		
		if (memberCmdTable(line)) { // remote olarak çalıştırılacak program
			
			/* get nodes' workload parameters
			*/
			workLoad= new WORKLOAD[nNodes];

			for (nit= nodes.begin(), i= 0; nit!= nodes.end(); nit++, i++) {

				if (DEBUG) printf("getting workload from (name= %s, ip= %s)\n",
					 (*nit).name, (*nit).ip);

				if (!readWorkload((*nit).ip, &workLoad[i])) {
					printf("WARNING: getting nodes workload failed =%s\n", (*nit).name);
					(*nit).status= DOWN;
				}				
				else if (DEBUG) printf("Successful\n\n");
			} 
			
			// nod'u belirle (load balancing)
			nodeName= makeDecision(); 
			printf("Process is working on the node : %s\n", nodeName);
			if (nodeName[0] == 0) {
				printf("cannot find appropriate node ...\n");
				continue;
			}
			
			// programı o nod'a gönder
			sprintf(command, "rcp %s %s:/bin", line, nodeName);
			system(command);
			
			// programı o nod' a çalısştır
			sprintf(command, "rsh %s %s", nodeName, line);
			system(command);
			
			// programı o nod'tan sil
			sprintf(command, "rsh %s \"rm -f /bin/%s\"", nodeName, line);
			system(command);
			
			delete workLoad;
		}
		else if (!strncmp(line, "exit", 4)) // Shell den çıkış
			break; 
		else if (strlen(line) == 0)
			continue;
		else { // yerel icra ettirilecek komut
			if (DEBUG) printf("It works local ...\n");
			system(line);
		}
	}
}

void readCmdTable()
{
	FILE *fin;
	char token[32];
	int i;
	
	if ((fin= fopen("cmdtable", "r")) == NULL) {
		printf("readCmd: Warning ! cmdtable reading failed\n");
		fflush(stdout);
	}
	
	for (i= 0 ; !feof(fin) ; i++) {
		fscanf(fin, "%s", token);
		strcpy(cmdTable[i], token);
	}
	nCmdTable= i;
}

bool memberCmdTable(char *line)
{
	int i;
	
	for (i= 0; i< nCmdTable; i++) {
		if (!strcmp(cmdTable[i], line))
			return true;
	}
	return false;
}

char *makeDecision()
{
	int i;
	double load= 10.0e20 , average;

	int selection= 0; // 0.average, 1.cpu, 2.memory 3. no of process
	char *nodeName;
	
	nodeName= new char[32];
	bzero((char *)nodeName, 32);
	
	nit= nodes.begin();
	for (i= 0; nit!= nodes.end(); nit++, i++) {
		
		if ((*nit).status == DOWN) continue;
		
		average= 0.0;

		switch(selection) {
			case 0 : // averaj cpu, mem, swap kullanımlarının ortalaması
				average+= workLoad[i].cpu_usage.average;
				average+= workLoad[i].mem_usage.average;
				average+= workLoad[i].swp_usage.average;
				
				average /= 3.0;
				
				if (DEBUG) printf("%s, %f\n", (*nit).name, average);

				if (load > average) {
					load= average;
					strcpy(nodeName, (*nit).name);
				}
				break;
			case 1 :
			case 2 :
			case 3 :
				break;
		}
	}
	return nodeName;
}

ssize_t writen(int fd, const void *vptr, size_t n)
{
	size_t 		nleft;
	ssize_t 	nwritten;
	const char 	*ptr;
	
	ptr= (const char *)vptr;
	nleft= n;
	while (nleft> 0) {
		if ( (nwritten= write(fd, ptr, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten= 0;
			else 
				return -1;
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return (n);
}

ssize_t readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char c, *ptr;
	
	ptr= (char *)vptr;
	for (n= 1; n< maxlen; n++) {
		if ( (rc= read(fd, &c, 1)) == 1) {
			*ptr++= c;
			if (c == '\n')
				break;				/* newline is trored like fgets */
		} else if (rc == 0) {
			if (n == 1)
				return 0;			/* EOF, no date read */
			else
				break;				/* some data were read */
		} else {
			if (errno == EINTR) {
				n--;
				continue;
			}
			return -1;				/* error, error no set by read() */
		}
	}
	*ptr = 0;
	return n;
}
