/**
 * This decision module intend to decide whether to offload before computing.
 * First, collecting environment factors, then building an approximate function
 * describing the cost value both on time and energy. Then, deciding which one is
 * benefit for user specified preference. Finally, do the computation.
 *
 */

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

extern float matrix_mult(const int, struct FactorSet *, float *, float *, float *);
extern float offload_to_gpu(const int, struct FactorSet *, float *, float *, float *);
extern float offload_to_cloud(const int, struct FactorSet *, float *, float *, float *);
static void compute(int, struct FactorSet *, const int);
static int make_decision(struct CostFunction *, float);

struct handler {
	char *unit;
	float (*function)(int, struct FactorSet *, float *, float *, float *);
} func[3] = {
	{(char *)"CPU",   matrix_mult},
	{(char *)"GPU",   offload_to_gpu},
	{(char *)"CLOUD", offload_to_cloud}
};

void decision_module(const char *param, float alpha, int specified)
{
	int errcode;
	int size = atoi(param);
	int op_unit;
	struct FactorSet factor_set;
	struct CostFunction cost_func;
	FILE *fptr = NULL;

	check_current_env();
		
	init_factor_set(&factor_set);

	if ((fptr = fopen(FACTOR_TABLE, "r")) == NULL) {
		/* Factor table not exist, first time execution */
		/* If folder "Docs" not exists, create it */
		if (access("Docs", 0) == -1) {
			if (mkdir("Docs", S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
				system_error();
			}
		}
		create_factor_table(&factor_set);
		op_unit = CPU;
	}
	else {
		debug_print("INFO", "Collecting Factors\n");
		collect_factors(&factor_set, fptr);
		fclose(fptr);
		factor_set.N_input = 2*size*size*sizeof(float);
		factor_set.N_output = size*size*sizeof(float); 

		debug_print("INFO", "Building Cost Functions\n");
		errcode = build_cost_functions(&factor_set, &cost_func, size);
		if (errcode != 0) {
			system_error();
		}

		debug_print("INFO", "Making Decision\n");
		op_unit = make_decision(&cost_func, alpha);

		/* If given specified target, then set the target for executing. */
		if (specified >= 0) {
			op_unit = specified;
		}
	}
	compute(op_unit, &factor_set, size);
	update_factor_table(&factor_set);
}

/*
 * @Function:		make_decision
 * @Input
 * 		cf:			cost functions
 * 		alpha:
 * @Description:	making decision according to cost functions
 * */
int make_decision(struct CostFunction *cf, float alpha)
{
	int i;
	float min_value = 0;
	float epsilon_cop_T, epsilon_cop_E;
	float epsilon_cld_T, epsilon_cld_E;
	float temp;
	int target = CPU;

	epsilon_cop_T = (cf->T_cop - cf->T_cpu)/cf->T_cpu;
	epsilon_cop_E = (cf->E_cop - cf->E_cpu)/cf->E_cpu;
	temp = alpha*epsilon_cop_T+(1-alpha)*epsilon_cop_E;
	if (min_value > temp) {
		min_value = temp;
		target = GPU;
	}
	printf("%f %f\n", epsilon_cop_T, epsilon_cop_E);

	epsilon_cld_T = (cf->T_cld - cf->T_cpu)/cf->T_cpu;
	epsilon_cld_E = (cf->E_cld - cf->E_cpu)/cf->E_cpu;
	temp = alpha*epsilon_cld_T+(1-alpha)*epsilon_cld_E;
	if (min_value > temp) {
		min_value = temp;
		target = CLOUD;
	}
	printf("%f %f\n", epsilon_cld_T, epsilon_cld_E);

	debug_print("INFO", "The minimum cost is use %s with %f us.\n", func[target].unit, min_value);

	FILE *fptr = fopen("Docs/dec-log", "a");
	fprintf(fptr, "%s\t", func[target].unit);
	fclose(fptr);

	return target;
}

/*
 * @Function:		compute
 * @Return:			none
 * @Input
 * 		op_unit:
 * 		fs:
 * 		times:
 * @Description:	
 */
static void compute(int op_unit, struct FactorSet *fs, const int size)
{
	float exec_time = 0.0f;
	FILE *fptr;

	/* Initialization, should be shifted to before */
	srand(time(NULL));
	float A[size*size], B[size*size], T[size*size];
	for (int i=0; i<size*size; i++) {
		A[i] = (rand()/rand_max)*2 - 1;
		B[i] = (rand()/rand_max)*2 - 1;
		T[i] = 0.0f;
	}

	exec_time = (func[op_unit].function)(size, fs, A, B, T);

	printf("%s spend: %f us\n", func[op_unit].unit, exec_time);
	/* Write execution time to log file */
	fptr = fopen("Docs/exec-log", "a");
	fprintf(fptr, " %f\n", exec_time);
	fclose(fptr);
}
