#include <stdlib.h>

#include "vtr_assert.h"

#include "ace.h"
#include "io_ace.h"

#include "base/abc/abc.h"

bool check_if_fanout_is_po(Abc_Ntk_t * ntk, Abc_Obj_t * obj);

char * hdl_name_ptr = NULL;

bool check_if_fanout_is_po(Abc_Ntk_t * /*ntk*/, Abc_Obj_t * obj) {
	Abc_Obj_t * fanout;
	int i;

	if (Abc_ObjIsCo(obj)) {
		return FALSE;
	}

	Abc_ObjForEachFanout(obj, fanout, i)
	{
		if (Abc_ObjIsPo(fanout)) {
			return TRUE;
		}
	}

	return FALSE;
}

void ace_io_print_activity(Abc_Ntk_t * ntk, FILE * fp) {
	Abc_Obj_t * obj;
	Abc_Obj_t * obj_new;
	int i;

	Abc_NtkForEachObj(ntk, obj, i)
	{
		VTR_ASSERT(obj->pCopy);
		obj_new = obj->pCopy;

		Ace_Obj_Info_t * info = Ace_ObjInfo(obj);
		//Ace_Obj_Info_t * info = malloc(sizeof(Ace_Obj_Info_t));

		char * name = NULL;

		if (check_if_fanout_is_po(ntk, obj)) {
			//continue;
		}

		switch (Abc_ObjType(obj)) {

		case ABC_OBJ_PI:

			name = Abc_ObjName(Abc_ObjFanout0(obj_new));
			break;

		case ABC_OBJ_BO:
			name = Abc_ObjName(Abc_ObjFanout0(obj_new));
			break;

		case ABC_OBJ_LATCH:
		case ABC_OBJ_PO:
		case ABC_OBJ_BI:
			break;

		case ABC_OBJ_NODE:
			name = Abc_ObjName(Abc_ObjFanout0(obj_new));
			//name = Abc_ObjName(obj);
			break;

		default:
			//printf("Unkown Type: %d\n", Abc_ObjType(obj));
			//VTR_ASSERT(0);
			break;
		}

		/*
		 if (Abc_ObjType(obj) == ABC_OBJ_BI || Abc_ObjType(obj) == ABC_OBJ_LATCH)
		 {
		 continue;
		 }

		 */

		if (name && strcmp(name, "unconn")) {
			if (fp != NULL) {
				//fprintf (fp, "%d-%d %s\n", Abc_ObjId(obj), Abc_ObjType(obj), name);
				//fprintf (fp, "%d-%d %s %f %f %f\n", Abc_ObjId(obj), Abc_ObjType(obj), name, info->static_prob, info->switch_prob, info->switch_act);
				fprintf(fp, "%s %f %f\n", name, info->static_prob,
						info->switch_act);

			} else {
				printf("%s %f %f\n", Abc_ObjName(obj), info->static_prob,
						info->switch_act);
			}
		}
	}
}

int ace_io_parse_argv(int argc, char ** argv, FILE ** BLIF, FILE ** IN_ACT,
		FILE ** OUT_ACT, char * blif_file_name, char * new_blif_file_name,
		ace_pi_format_t * pi_format, double *p, double * d, int * seed, char** clk_name) {
	int i;
	char option;

	if (argc <= 1) {
		printf("Error: no parameters specified.\n");
		ace_io_print_usage();
		exit(1);
	}

	i = 1;
	while (i < argc) {
		if (argv[i][0] == '-') {
			option = argv[i][1];
			i++;
			switch (option) {
			case 'b':
				*BLIF = fopen(argv[i], "r");
				strncpy(blif_file_name, argv[i], BLIF_FILE_NAME_LEN - 1);
				blif_file_name[strlen(argv[i])] = '\0';
				break;
			case 'n':
				strncpy(new_blif_file_name, argv[i], BLIF_FILE_NAME_LEN - 1);
				new_blif_file_name[strlen(argv[i])] = '\0';
				break;
			case 'o':
				*OUT_ACT = fopen(argv[i], "w");
				break;
			case 'a':
				*pi_format = ACE_ACT;
				*IN_ACT = fopen(argv[i], "r");
				break;
			case 'v':
				*pi_format = ACE_VEC;
				*IN_ACT = fopen(argv[i], "r");
				break;
			case 'p':
				*pi_format = ACE_PD;
				*p = atof(argv[i]);
				break;
			case 'd':
				*pi_format = ACE_PD;
				*d = atof(argv[i]);
				break;
			case 's':
				*seed = atoi(argv[i]);
				break;
			case 'c':
				*clk_name = argv[i];
				break;
			default:
				ace_io_print_usage();
				exit(1);
			}
		}
		i++;
	}

	if (*BLIF == NULL) {
        printf("No BLIF file specified\n");
		ace_io_print_usage();
		exit(1);
	}

	if (*clk_name == NULL) {
        printf("No clock specified\n");
		ace_io_print_usage();
		exit(1);
	}
	return 0;
}

void ace_io_print_usage() {
	(void) fprintf(stderr, "usage: ace\n");

	(void) fprintf(stderr, "                                --+\n");
	(void) fprintf(stderr, "    -b [input circuitname.blif]   | required\n");
	(void) fprintf(stderr, "    -c [clock name]               | required\n");
	(void) fprintf(stderr, "    -n [new circuitname.blif]     |\n");
	(void) fprintf(stderr, "                                --+\n");
	(void) fprintf(stderr, "\n");
	(void) fprintf(stderr, "                                --+\n");
	(void) fprintf(stderr, "    -o [output activity filename] | optional\n");
	(void) fprintf(stderr, "                                --+\n");
	(void) fprintf(stderr, "\n");
	(void) fprintf(stderr, "                                --+\n");
	(void) fprintf(stderr, "    -a [input activity filename]  | optional\n");
	(void) fprintf(stderr, "          or                      |\n");
	(void) fprintf(stderr, "    -v [input vector filename]    |\n");
	(void) fprintf(stderr, "          or                      |\n");
	(void) fprintf(stderr, "    -p [PI static probability]    |\n");
	(void) fprintf(stderr, "    -d [PI switching activity]    |\n");
	(void) fprintf(stderr, "                                --+\n");
}

int ace_io_read_activity(Abc_Ntk_t * ntk, FILE * in_file_desc,
		ace_pi_format_t pi_format, double p, double d, const char * clk_name) {
	int error = 0;
	int i;
	Abc_Obj_t * obj_ptr;
	Ace_Obj_Info_t * info;
	Nm_Man_t * name_manager = ntk->pManName;
	int num_Pi = Abc_NtkPiNum(ntk);

	printf("Name Manager Entries: %d\n", Nm_ManNumEntries(name_manager));

	/*
	 int clk_pi_obj_id = Nm_ManFindIdByName(name_manager, (char *) clk_name, ABC_OBJ_PI);
	 printf ("Clk PI ID: %d\n", clk_pi_obj_id);

	 Abc_Obj_t * clk_obj_ptr;
	 clk_obj_ptr = Abc_NtkObj(ntk, clk_pi_obj_id);
	 printf("Clock Fanouts: %d\n", clk_obj_ptr->vFanouts.nSize);
	 */

	Vec_Ptr_t * node_vec;
	node_vec = Abc_NtkDfsSeq(ntk);

	// Initialize node information structure
	Vec_PtrForEachEntry(Abc_Obj_t*, node_vec, obj_ptr, i)
	{
		info = Ace_ObjInfo(obj_ptr);
		info->static_prob = ACE_OPEN;
		info->switch_prob = ACE_OPEN;
		info->switch_act = ACE_OPEN;
	}

	//info = Ace_InfoPtrGet(clk_obj_ptr);
	//info->static_prob = 0.5;
	//info->switch_prob = 1.0;
	//info->switch_act = 1.0;

	if (in_file_desc == NULL) {
		if (pi_format == ACE_ACT || pi_format == ACE_VEC) {
			printf("Cannot open input file\n");
			error = ACE_ERROR;
		} else {
			VTR_ASSERT(p >= 0.0 && p <= 1.0);
			VTR_ASSERT(d >= 0.0 && d <= 1.0);
			VTR_ASSERT(d <= 2.0 * p);
			VTR_ASSERT(d <= 2.0 * (1.0 - p));

			Abc_NtkForEachPi(ntk, obj_ptr, i)
			{
				info = Ace_ObjInfo(obj_ptr);

				if (strcmp(Abc_ObjName(obj_ptr), clk_name) == 0) {
					info->static_prob = 0.5;
					info->switch_prob = 1;
					info->switch_act = 2;
				} else {
					info->static_prob = p;
					info->switch_prob = d;
					info->switch_act = d;
				}
			}
		}
	} else {
		char line[ACE_CHAR_BUFFER_SIZE];

		if (pi_format == ACE_ACT) {
			char pi_name[ACE_CHAR_BUFFER_SIZE];
			double static_prob, switch_prob;
			Abc_Obj_t * pi_obj_ptr;
			int pi_obj_id;
            char* res;

			printf("Reading activity file...\n");

			// Read real PIs activity values from file
			res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
            VTR_ASSERT(res);
			while (!feof(in_file_desc)) {
				sscanf(line, "%s %lf %lf\n", pi_name, &static_prob,
						&switch_prob);

				pi_obj_id = Nm_ManFindIdByName(name_manager, pi_name,
						ABC_OBJ_PI);
				if (pi_obj_id == -1) {
					printf("Primary input %s does not exist\n", pi_name);
					error = ACE_ERROR;
					break;
				}
				pi_obj_ptr = Abc_NtkObj(ntk, pi_obj_id);

				VTR_ASSERT(static_prob >= 0.0 && static_prob <= 1.0);
				VTR_ASSERT(switch_prob >= 0.0 && switch_prob <= 1.0);

				info = Ace_ObjInfo(pi_obj_ptr);
				info->static_prob = static_prob;
				info->switch_prob = switch_prob;
				info->switch_act = switch_prob;

				res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
                VTR_ASSERT(res);
			}
		} else if (pi_format == ACE_VEC) {
			printf("Reading vector file...\n");

			int num_vec = 0;
			int * high;
			int * toggles;
			int * current;
			char vector[ACE_CHAR_BUFFER_SIZE];
            char* res;

			res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
            VTR_ASSERT(res);
			while (!feof(in_file_desc)) {
				res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
                VTR_ASSERT(res);
				num_vec++;
			}
			Abc_NtkForEachPi(ntk, obj_ptr, i)
			{
				info = Ace_ObjInfo(obj_ptr);
				info->values = (int *) malloc(num_vec * sizeof(int));
			}
			fseek(in_file_desc, 0, SEEK_SET);

			high = (int *) calloc(num_Pi, sizeof(int));
			toggles = (int *) calloc(num_Pi, sizeof(int));
			current = (int *) calloc(num_Pi, sizeof(int));


			num_vec = 0;
			res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
            VTR_ASSERT(res);
			while (!feof(in_file_desc)) {
				sscanf(line, "%s\n", vector);

				if (strlen(vector) != num_Pi) {
					printf(
							"Error: vector length (%zu) doesn't match number of inputs (%d)\n",
							strlen(vector), num_Pi);
					error = ACE_ERROR;
					break;
				}
				VTR_ASSERT(strlen(vector) == num_Pi);

				if (num_vec == 0) {
					Abc_NtkForEachPi(ntk, obj_ptr, i)
					{
						high[i] = (vector[i] == '1');
						toggles[i] = 0;
						current[i] = (vector[i] == '1');
					}
				} else {
					Abc_NtkForEachPi(ntk, obj_ptr, i)
					{
						high[i] += (vector[i] == '1');
						if ((vector[i] == '0' && current[i])
								|| (vector[i] == '1' && !current[i])) {
							toggles[i]++;
						}
						current[i] = (vector[i] == '1');
					}
				}

				Abc_NtkForEachPi(ntk, obj_ptr, i)
				{
					info = Ace_ObjInfo(obj_ptr);
					info->values[num_vec] = (vector[i] == '1');
				}

				res = fgets(line, ACE_CHAR_BUFFER_SIZE, in_file_desc);
                VTR_ASSERT(res);
				num_vec++;
			}

			if (!error) {
				Abc_NtkForEachPi(ntk, obj_ptr, i)
				{
                    VTR_ASSERT(num_vec > 0);

					info = Ace_ObjInfo(obj_ptr);
					info->static_prob = (double) high[i] / (double) num_vec;
					info->static_prob = MAX(0.0, info->static_prob);
					info->static_prob = MIN(1.0, info->static_prob);

					info->switch_prob = (double) toggles[i] / (double) num_vec;
					info->switch_prob = MAX(0.0, info->switch_prob);
					info->switch_prob = MIN(1.0, info->switch_prob);

					info->switch_act = info->switch_prob;
				}
			}

			free(toggles);
			free(high);
			free(current);
		} else {
			printf("Error: Unkown activity file format.\n");
			error = ACE_ERROR;
		}
	}

	Vec_PtrFree(node_vec);

	return error;
}