#include "vtr_assert.h"

#include "ace.h"
#include "sim.h"

#include "bdd/cudd/cudd.h"
#include "bdd/cudd/cuddInt.h"

void get_pi_values(Abc_Ntk_t * ntk, Vec_Ptr_t * nodes, int cycle);
int * getFaninValues(Abc_Obj_t * obj_ptr);
ace_status_t getFaninStatus(Abc_Obj_t * obj_ptr);
void evaluate_circuit(Abc_Ntk_t * ntk, Vec_Ptr_t * node_vec, int cycle);
void update_FFs(Abc_Ntk_t * ntk);

void get_pi_values(Abc_Ntk_t * ntk, Vec_Ptr_t * /*nodes*/, int cycle) {
	Abc_Obj_t * obj;
	Ace_Obj_Info_t * info;
	int i;
	double prob0to1, prob1to0, rand_num;

	//Vec_PtrForEachEntry(Abc_Obj_t *, nodes, obj, i)
	Abc_NtkForEachObj(ntk, obj, i)
	{
		info = Ace_ObjInfo(obj);
		if (Abc_ObjType(obj) == ABC_OBJ_PI) {
			if (info->values) {
				if (info->status == ACE_UNDEF) {
					info->status = ACE_NEW;
					if (info->values[cycle] == 1) {
						info->value = 1;
						info->num_toggles = 1;
						info->num_ones = 1;
					} else {
						info->value = 0;
						info->num_toggles = 0;
						info->num_ones = 0;
					}
				} else {
					switch (info->value) {
					case 0:
						if (info->values[cycle] == 1) {
							info->value = 1;
							info->status = ACE_NEW;
							info->num_toggles++;
							info->num_ones++;
						} else {
							info->status = ACE_OLD;
						}
						break;

					case 1:
						if (info->values[cycle] == 0) {
							info->value = 0;
							info->status = ACE_NEW;
							info->num_toggles++;
						} else {
							info->num_ones++;
							info->status = ACE_OLD;
						}
						break;

					default:
						printf("Bad Value\n");
						VTR_ASSERT(0);
						break;
					}
				}
			} else {
				prob0to1 = ACE_P0TO1(info->static_prob, info->switch_prob);
				prob1to0 = ACE_P1TO0(info->static_prob, info->switch_prob);

                //We don't need a cryptographically secure random number
                //generator so suppress warning in coverity
                //
                //coverity[dont_call]
				rand_num = (double) rand() / (double) RAND_MAX;

				if (info->status == ACE_UNDEF) {
					info->status = ACE_NEW;
					if (rand_num < prob0to1) {
						info->value = 1;
						info->num_toggles = 1;
						info->num_ones = 1;
					} else {
						info->value = 0;
						info->num_toggles = 0;
						info->num_ones = 0;
					}
				} else {
					switch (info->value) {
					case 0:
						if (rand_num < prob0to1) {
							info->value = 1;
							info->status = ACE_NEW;
							info->num_toggles++;
							info->num_ones++;
						} else {
							info->status = ACE_OLD;
						}
						break;

					case 1:
						if (rand_num < prob1to0) {
							info->value = 0;
							info->status = ACE_NEW;
							info->num_toggles++;
						} else {
							info->num_ones++;
							info->status = ACE_OLD;
						}
						break;

					default:
						printf("Bad value\n");
						VTR_ASSERT(FALSE);
						break;
					}
				}
			}
		}
	}
}

int * getFaninValues(Abc_Obj_t * obj_ptr) {
	Abc_Obj_t * fanin;
	int i;
	Ace_Obj_Info_t * info;
	int * faninValues;

	Abc_ObjForEachFanin(obj_ptr, fanin, i)
	{
		info = Ace_ObjInfo(fanin);
		if (info->status == ACE_UNDEF) {
			printf("Fan-in is undefined\n");
			VTR_ASSERT(FALSE);
		} else if (info->status == ACE_NEW) {
			break;
		}
	}

	if (i >= Abc_ObjFaninNum(obj_ptr)) {
		// inputs haven't changed
		return NULL;
	}

	faninValues = (int*) malloc(Abc_ObjFaninNum(obj_ptr) * sizeof(int));
	Abc_ObjForEachFanin(obj_ptr, fanin, i)
	{
		info = Ace_ObjInfo(fanin);
		faninValues[i] = info->value;
	}

	return faninValues;
}

ace_status_t getFaninStatus(Abc_Obj_t * obj_ptr) {
	Abc_Obj_t * fanin;
	int i;
	Ace_Obj_Info_t * info;

	Abc_ObjForEachFanin(obj_ptr, fanin, i)
	{
		info = Ace_ObjInfo(fanin);
		if (info->status == ACE_UNDEF) {
			return ACE_UNDEF;
		}
	}

	Abc_ObjForEachFanin(obj_ptr, fanin, i)
	{
		info = Ace_ObjInfo(fanin);
		if (info->status == ACE_NEW || info->status == ACE_SIM) {
			return ACE_NEW;
		}
	}

	return ACE_OLD;
}

void evaluate_circuit(Abc_Ntk_t * ntk, Vec_Ptr_t * node_vec, int /*cycle*/) {
	Abc_Obj_t * obj;
	Ace_Obj_Info_t * info;
	int i;
	int value = -1;
	int * faninValues;
	ace_status_t status;
	DdNode * dd_node;

	Vec_PtrForEachEntry(Abc_Obj_t*, node_vec, obj, i)
	{
		info = Ace_ObjInfo(obj);

		switch (Abc_ObjType(obj)) {
		case ABC_OBJ_PI:
		case ABC_OBJ_BO:
			break;

		case ABC_OBJ_PO:
		case ABC_OBJ_BI:
		case ABC_OBJ_LATCH:
		case ABC_OBJ_NODE:
			status = getFaninStatus(obj);
			switch (status) {
			case ACE_UNDEF:
				info->status = ACE_UNDEF;
				break;
			case ACE_OLD:
				info->status = ACE_OLD;
				info->num_ones += info->value;
				break;
			case ACE_NEW:
				if (Abc_ObjIsNode(obj)) {
					faninValues = getFaninValues(obj);
					VTR_ASSERT(faninValues);
					dd_node = Cudd_Eval((DdManager*) ntk->pManFunc, (DdNode*) obj->pData, faninValues);
					VTR_ASSERT(Cudd_IsConstant(dd_node));
					if (dd_node == Cudd_ReadOne((DdManager*) ntk->pManFunc)) {
						value = 1;
					} else if (dd_node == Cudd_ReadLogicZero((DdManager*) ntk->pManFunc)) {
						value = 0;
					} else {
						VTR_ASSERT(0);
					}
					free(faninValues);
				} else {
					Ace_Obj_Info_t * fanin_info = Ace_ObjInfo(
							Abc_ObjFanin0(obj));
					value = fanin_info->value;
				}

				if (info->value != value || info->status == ACE_UNDEF) {
					info->value = value;
					if (info->status != ACE_UNDEF) {
						/* Don't count the first value as a toggle */
						info->num_toggles++;
					}
					info->status = ACE_NEW;
				} else {
					info->status = ACE_OLD;
				}
				info->num_ones += info->value;
				break;
			default:
				VTR_ASSERT(0);
				break;
			}
			break;
		default:
			VTR_ASSERT(0);
			break;
		}
	}
}

void update_FFs(Abc_Ntk_t * ntk) {
	Abc_Obj_t * obj;
	int i;
	Ace_Obj_Info_t * bi_fanin_info;
	Ace_Obj_Info_t * bi_info;
	Ace_Obj_Info_t * latch_info;
	Ace_Obj_Info_t * bo_info;

	Abc_NtkForEachLatch(ntk, obj, i)
	{
		bi_fanin_info = Ace_ObjInfo(Abc_ObjFanin0(Abc_ObjFanin0(obj)));
		bi_info = Ace_ObjInfo(Abc_ObjFanin0(obj));
		bo_info = Ace_ObjInfo(Abc_ObjFanout0(obj));
		latch_info = Ace_ObjInfo(obj);

		// Value
		bi_info->value = bi_fanin_info->value;
		latch_info->value = bi_fanin_info->value;
		bo_info->value = bi_fanin_info->value;

		// Status
		bi_info->status = bi_fanin_info->status;
		latch_info->status = bi_fanin_info->status;
		bo_info->status = bi_fanin_info->status;

		// Ones
		bi_info->num_ones = bi_fanin_info->num_ones;
		latch_info->num_ones = bi_fanin_info->num_ones;
		bo_info->num_ones = bi_fanin_info->num_ones;

		// Toggles
		bi_info->num_toggles = bi_fanin_info->num_toggles;
		latch_info->num_toggles = bi_fanin_info->num_toggles;
		bo_info->num_toggles = bi_fanin_info->num_toggles;
	}
}

void ace_sim_activities(Abc_Ntk_t * ntk, Vec_Ptr_t * nodes, int max_cycles,
		double threshold) {
	Abc_Obj_t * obj;
	Ace_Obj_Info_t * info;
	int i;

	VTR_ASSERT(max_cycles > 0);
	VTR_ASSERT(threshold > 0.0);

//	srand((unsigned) time(NULL));

	//Vec_PtrForEachEntry(Abc_Obj_t *, nodes, obj, i)
	Abc_NtkForEachObj(ntk, obj, i)
	{
		info = Ace_ObjInfo(obj);
		info->value = 0;

		if (Abc_ObjType(obj) == ABC_OBJ_BO) {
			info->status = ACE_NEW;
		} else {
			info->status = ACE_UNDEF;
		}
		info->num_ones = 0;
		info->num_toggles = 0;
	}

	Vec_Ptr_t * logic_nodes = Abc_NtkDfs(ntk, TRUE);
	for (i = 0; i < max_cycles; i++) {
		get_pi_values(ntk, nodes, i);
		evaluate_circuit(ntk, logic_nodes, i);
		update_FFs(ntk);
	}

	//Vec_PtrForEachEntry(Abc_Obj_t *, nodes, obj, i)
	Abc_NtkForEachObj(ntk, obj, i)
	{
		info = Ace_ObjInfo(obj);
		info->static_prob = info->num_ones / (double) max_cycles;
		VTR_ASSERT(info->static_prob >= 0.0 && info->static_prob <= 1.0);
		info->switch_prob = info->num_toggles / (double) max_cycles;
		VTR_ASSERT(info->switch_prob >= 0.0 && info->switch_prob <= 1.0);

		VTR_ASSERT(info->switch_prob - EPSILON <= 2.0 * (1.0 - info->static_prob));
		VTR_ASSERT(info->switch_prob - EPSILON <= 2.0 * (info->static_prob));

		info->status = ACE_SIM;
	}
    Vec_PtrFree(logic_nodes);
}