#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "base.h"
#include "factor.h"

static float collect_cop_capability();
static float collect_cloud_capability();
static void show_factors(struct FactorSet *);

/*
 * @Function:		init_factor_set
 * @Return:			none
 * @Input
 * 		fs:			pointer to factor set data structure
 * @Description:	Initialize the factor set data
 */
void init_factor_set(struct FactorSet *fs)
{
	fs->B_tx = 0.0f;
	fs->B_rx = 0.0f;
	fs->t_comp = 0.0f;
	fs->N_input = 0.0f;
	fs->N_output = 0.0f;
	fs->U_cpu = 0.0f;
	fs->U_cop = 0.0f;
	fs->U_cld = 0.0f;
	fs->U_mem = 0.0f;
	fs->P_basic = 0.886;
	fs->P_cpu = 0.653;
	fs->P_cop = 0.17; 
	fs->P_nic = 2.262;
	memset(fs->server_addr, 0, sizeof(fs->server_addr));
	fs->server_port = 0;
}

/*
 * @Function:		create_factor_table
 * @Return:			none
 * @Input
 * 		fs:			factor set
 * @Description:	For the first time execution, collecting values of factors
 *
 * */
void create_factor_table(struct FactorSet *fs)
{
	FILE *fptr = fopen(FACTOR_TABLE, "w");
	if (!fptr)
		system_error();
	fclose(fptr);

	fs->U_mem = collect_mem_capability();
	fs->U_cpu = collect_cpu_capability();
	fs->U_cop = collect_cop_capability();
	fs->U_cld = collect_cloud_capability();
	strcpy(fs->server_addr, "140.113.88.148");
	fs->server_port = 23240;
}


/*
 * @Function:		update_factor_table
 * @Return:			none
 * @Input
 * 		fs:			factor set
 * @Description:	Given factor structure, write new value of factors
 * 					into factor table
 * */
void update_factor_table(struct FactorSet *fs)
{
	FILE *fptr;
	
	fptr = fopen(FACTOR_TABLE, "w");
	if (!fptr)
		system_error();

	fprintf(fptr, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
	fprintf(fptr, "<FactorTable>\n");
	fprintf(fptr, "    <factor name=\"B_tx\">%f</factor>\n", fs->B_tx);
	fprintf(fptr, "    <factor name=\"B_rx\">%f</factor>\n", fs->B_rx);
	fprintf(fptr, "    <factor name=\"t_comp\">%f</factor>\n", fs->t_comp);
	fprintf(fptr, "    <factor name=\"N_input\">%f</factor>\n", fs->N_input);
	fprintf(fptr, "    <factor name=\"N_output\">%f</factor>\n", fs->N_output);
	fprintf(fptr, "    <factor name=\"U_mem\">%f</factor>\n", fs->U_mem);
	fprintf(fptr, "    <factor name=\"U_cpu\">%f</factor>\n", fs->U_cpu);
	fprintf(fptr, "    <factor name=\"U_cop\">%f</factor>\n", fs->U_cop);
	fprintf(fptr, "    <factor name=\"U_cld\">%f</factor>\n", fs->U_cld);
	fprintf(fptr, "    <factor name=\"server_addr\">%s</factor>\n", fs->server_addr);
	fprintf(fptr, "    <factor name=\"server_port\">%d</factor>\n", fs->server_port);
	fprintf(fptr, "</FactorTable>\n");

	fclose(fptr);
}


/*
 * @Function:		collect_factors
 * @Input: 
 *		f:			pointer of factor set structure
 *		fptr:
 * @Return:			none
 * @Description:	Given a factor structure, iteratively reading factors from 
 * 					file, and re-fetching bandwidth.
 * */
void collect_factors(struct FactorSet *f, FILE *fptr)
{
	char name[20], value[20];

	/* Iteratively reading factors from file. */

	fscanf(fptr, "%*[^\n]\n");
	fscanf(fptr, "%*[^\n]\n");	// XML Header
	while (1) {
		int temp = fscanf(fptr, "%*[^\"]\"%[^\"]\">%[^<]%*[^\n]\n", name, value);
		if (temp == -1)
			break;

		if (!strcmp(name, "B_tx"))
			f->B_tx = atof(value);
		else if (!strcmp(name, "B_rx"))
			f->B_rx = atof(value);
		else if (!strcmp(name, "t_comp"))
			f->t_comp = atof(value);
		else if (!strcmp(name, "N_input"))
			f->N_input = atof(value);
		else if (!strcmp(name, "N_output"))
			f->N_output = atof(value);
		else if (!strcmp(name, "U_mem"))
			f->U_mem = atof(value);
		else if (!strcmp(name, "U_cpu"))
			f->U_cpu = atof(value);
		else if (!strcmp(name, "U_cop"))
			f->U_cop = atof(value);
		else if (!strcmp(name, "U_cld"))
			f->U_cld = atof(value);
		else if (!strcmp(name, "server_port"))
			f->server_port = atoi(value);
		else if (!strcmp(name, "server_addr")) {
			strcpy(f->server_addr, value);
		}
	}

	/* Collaborate bandwidth prediction */
	float alpha = 0.3;
	f->B_tx = f->B_tx*(1-alpha)+collect_bandwidth(f->server_addr)*(alpha);
	f->B_rx = f->B_tx;
#ifdef DEBUG
	show_factors(f);
#endif
}


/*
 * @Function:		collect_bandwidth
 * @param:
 * 		server		server name or server address, e.g. "www.google.com"
 * @Return:			the average bandwidth in Kbps
 * @Desciption:		Send "ping" packets to server, collecting the result by popen.
 * 					payload size is 6000, and it will be splitted into 4 ip packets
 * 					and one icmp packets (by fragmentation).
 * 					Header  Payload
 * 					------ -------
 * 					34      1480
 *
 * 					Header  Payload
 * 					------ -------
 * 					42      80
 * */
float collect_bandwidth(const char *server)
{
	char buff[100], command[50], result[50];
	int ping_count = 10;
	int payload_size = 6000;
	int total_bytes;
	FILE *fptr;

	memset(command, 0, sizeof(command));
	sprintf(command, "ping -c %d -s %d -i 0.1 %s", ping_count, payload_size, server);
	if (!(fptr = popen(command, "r"))) {
		system_error();
	}

	// Read each line of ping result, we just want the last one.
	// e.g.: "rtt min/avg/max/mdev = 8.398/9.563/12.623/1.556 ms"
	while (fgets(buff, 100, fptr) != NULL) {
		;
	}
	pclose(fptr);

	if (buff[0]!='r' && buff[1]!='t' && buff[2]!='t') {
		debug_print("ERROR", "Destination unreachable.\n");
		exit(1);
	}

	total_bytes = (payload_size/1480)*(34+1480) + (42+payload_size%1480);
	sscanf(buff, "%*s %*s = %*[^/]/%[^/]/%*[^/]/%*[^/] ms", result);
	debug_print("INFO", "ping %s in %f ms\n", server, atof(result));
	return (float)(total_bytes*8*4)/atof(result);
}


/*
 * @Function:		collect_mem_capability
 * @Return:			memory speed in Mbps
 * @Description:	
 * */
float collect_mem_capability()
{
	int i, j;
	int move_times = 50;
	char *target;
	char temp;
	float exec_time;
	struct timespec start, stop;

	target = (char *)malloc(ONE_M);
	if (!target) {
		system_error();
	}  

	for (i=0; i<ONE_M; i++) {
		target[i] = i%100;
	}

	clock_gettime(CLOCK_MONOTONIC, &start);
	for (i=0; i<move_times; i++) {
		for (j=0; j<ONE_M; j++) {
			temp = target[j] + 1;
		}
	}
	clock_gettime(CLOCK_MONOTONIC, &stop);
	exec_time = calc_diff_time(&start, &stop);

	target[(int)temp] = 3;

	free(target);

	return ((float)ONE_M*move_times*8*ONE_K)/exec_time;	/* Mbps */
}

float collect_cpu_capability()
{
	char buff[50], result[50];
	FILE *fptr;
	int errcode;

	fptr = fopen("/proc/cpuinfo", "r");
	if (fptr == NULL) {
		system_error();
	}

	/*
	 * Read each line of /proc/cpuinfo, we just want value of BogoMIPS.
	 * e.g.: "BogoMIPS	: 162.54"
	 */
	while (1) {
		errcode = fscanf(fptr, "%[^\t]%*[^:]: %[^\n]%*c", buff, result);
		if (errcode == EOF) {
			system_error();
		}

		if (!strcmp(buff, "BogoMIPS")) {
			fclose(fptr);
			return atof(result);
		}
	}
}


static float collect_cop_capability()
{
	return 1000;
}

static float collect_cloud_capability()
{
	return 10;
}


/* This function is implemented for debugging. */
void show_factors(struct FactorSet *fs)
{
	debug_print("FACT", "B_tx: \t\t%f \tkbps\n", fs->B_tx);
	debug_print("FACT", "B_rx: \t\t%f \tkbps\n", fs->B_rx);
	debug_print("FACT", "t_comp: \t%f \tus\n", fs->t_comp);
//	debug_print("FACT", "N_input: \t%f \tBytes\n", fs->N_input);
//	debug_print("FACT", "N_output: \t%f \tBytes\n", fs->N_output);
	debug_print("FACT", "U_cpu: \t\t%f \tMHz\n", fs->U_cpu);
	debug_print("FACT", "U_cop: \t\t%f \tMHz\n", fs->U_cop);
	debug_print("FACT", "U_cld: \t\t%f \tMHz\n", fs->U_cld);
	debug_print("FACT", "U_mem: \t\t%f \tMHz\n", fs->U_mem);
//	debug_print("FACT", "server_addr: \t%s\n", fs->server_addr);
//	debug_print("FACT", "server_port: \t%d\n", fs->server_port);
}
