#include "ap_validator.h"


/* roughtly "equal" and "not equal" operator for ap_signal*/
inline bool operator ==(ap_signal const & p1, ap_signal const & p2)
{
	if (p1.fft_num != p2.fft_num)
		return false;
	if (p1.peak_bin != p2.peak_bin)
		return false;
	if (p1.scale != p2.scale)
		return false;
	if (p1.ffa_scale != p2.ffa_scale)
	        return false;
	if (p1.dm != p2.dm)
		return false;
	if (rel_diff(p1.peak_power, p2.peak_power) > TOLERANCE_PEAK_POWER)
		return false;
	if (abs_diff(p1.time, p2.time) > TOLERANCE_TIME)
		return false;
	if (rel_diff(p1.period, p2.period) > TOLERANCE_PERIOD)
		return false;
	return true;
}

inline bool operator !=(ap_signal const & p1, ap_signal const & p2)
{
	return !(p1 == p2);
}

/* operator overloding for ap_signal */
/*ostream& operator<<( ostream& os, const ap_signal& p1)
{
  os << "ap_signal->FFT_NUM: " << p1.fft_num << std::endl
	<< "ap_signal->PEAK_BIN: " << p1.peak_bin << std::endl
	<< "ap_signal->SCALE: " << p1.scale << std::endl
	<< "ap_signal->FFA_SCALE: " << p1.ffa_scale << std::endl
	<< "ap_signal->DM: " << p1.dm << std::endl
	<< "ap_signal->PEAK_POWER: " << p1.peak_power << std::endl
	<< "ap_signal->TIME: " << p1.time << std::endl
	<< "ap_signal->PERIOD: " << p1.period << std::endl;
  return os;
}*/

inline double rel_diff(double x1, double x2)
{
	if (x1 == 0 && x2 == 0)
		return 0;
	if (x1 == 0)
		return 1;
	if (x2 == 0)
		return 1;

	double d1 = fabs(x1);
	double d2 = fabs(x2);
	double d = fabs(x1 - x2);
	if (d1 > d2)
		return d / d1;
	return d / d2;
}

inline double abs_diff(double x1, double x2)
{
	return fabs(x1 - x2);
}

bool has_roughly_equal_signal(vector< ap_signal> const & p1, ap_signal const & p2)
{
	unsigned int i;
	for (i = 0; i < p1.size(); i++)
		if (p1[i] == p2)
			return true;
	return false;
}


/* return true if each pulse from each result is roughly equal
   to a pulse from the other result*/
bool strongly_similar(vector< ap_signal> const & p1, vector< ap_signal> const & p2)
{
	unsigned int i;
	for (i = 0; i < p1.size(); i++)
		if (!has_roughly_equal_signal(p2, p1[i]))
			return false;

	for (i = 0; i < p2.size(); i++)
		if (!has_roughly_equal_signal(p1, p2[i]))
			return false;

	return true;
}

/* return true if at least half the pulses (and at least one)
   from each result are roughly equal to a pulse from the other result*/

bool weakly_similar(vector< ap_signal> const & p1, vector< ap_signal> const & p2)
{
	unsigned int n1, n2;
	unsigned int m1, m2;
	unsigned int i;

	n1 = p1.size();
	n2 = p2.size();

	m1 = 0;
	for (i = 0; i < p1.size(); i++) {
		if (has_roughly_equal_signal(p2, p1[i])) {
			m1++;
		}
	}
	m2 = 0;
	for (i = 0; i < p2.size(); i++) {
		if (has_roughly_equal_signal(p1, p2[i])) {
			m2++;
		}
	}
	if (m1 == 0)
		return false;
	if (m2 == 0)
		return false;
	if (m1 < (n1 + 1) / 2)
		return false;
	if (m2 < (n2 + 1) / 2)
		return false;
	return true;
}

/*/////////////////////////////////////////////////////////////////////
//  		ap_checkset class   			//
/////////////////////////////////////////////////////////////////////*/

void ap_checkset::init(vector< RESULT>& result_vec, int min_quorum)
{
	//vector of ResultFile class.
	vector< ResultFile> ap_result_vec;
	double sum = 0; //temporary summation credit.
	double max_credit = 0, min_credit = 0;
	int max_credit_i = 0, min_credit_i = 0;
	int nvalid = 0;
	int i, j, k, m;
	int canonical_index = 0;  //temporay canonical index number.  
	bool retval;

	//Create the ResultFile vector.
	for (i = 0; i < result_vec.size(); i++) {
		ResultFile tmp;
		try {
			tmp = ResultFile(result_vec[i]);
		} catch (ResultFileError &rerr) {
			result_vec[i].outcome = RESULT_OUTCOME_VALIDATE_ERROR;
			result_vec[i].validate_state = VALIDATE_STATE_INVALID;
			continue;
		}

		/* keep ResultFile class in the vector and set validate state */
		//if (tmp->has_pulse && (tmp->pulses.size() > 0)){
		//if (tmp->has_pulse) {
		if (tmp.has_pulse) {
		//if (tmp) {
			ap_result_vec.push_back(tmp);
			// result_vec[i].validate_state = VALIDATE_STATE_VALID;
#ifdef TEST
			std::cout << "[RESULT#" << result_vec[i].id << "] PUSHED BACK" << std::endl;	
			std::cout << "obj.size() = " << ap_result_vec.size()
				<< std::endl;
#endif
		}
	}

	if (ap_result_vec.size() < min_quorum) {
		log_messages.printf(SCHED_MSG_LOG::MSG_DEBUG,
						"AP_CHECKSET: The number of good result file object is less than min_quorum.");
		canonical_id = 0;
		has_canonical_result = false;
		retry = true;
		return;
	}

	//Strongly similar comparison.
	for (i = 0; i < ap_result_vec.size() - 1; i++) {
		if (has_canonical_result)
			break;  	
		for (j = i + 1; j < ap_result_vec.size(); j++) {
			if (has_canonical_result)
				break;  	
			stats.nstrong_compare++;	  
			retval = strongly_similar(ap_result_vec[i].pulses,
						ap_result_vec[j].pulses);
			if (retval) {
				has_canonical_result = true;
				stats.nstrong++;
				retry = false;
				/* set the canonical_id */
				canonical_id = ap_result_vec[i].result_id;
				canonical_index = i;

				/* set validate state for strongly similar result.*/
				ap_result_vec[i].validate_state = VALIDATE_STATE_VALID;
				ap_result_vec[j].validate_state = VALIDATE_STATE_VALID;

				/*at this point, i want to assure "result_vec[x]" and ap_result_vec[y] has the same result.id.*/
				/*because "result_vec[x]" and ap_result_vec[y] may not indicate to the same result.id when IO error occur.*/
				for (m = 0; m < result_vec.size(); m++) {
					if ((result_vec[m].id == ap_result_vec[i].result_id) ||
						(result_vec[m].id == ap_result_vec[j].result_id)){
						//std::cout << "[RESULT#" << result_vec[m].id << "]# VALID (STRONGLY SIMILAR)" << std::endl;
						result_vec[m].validate_state = VALIDATE_STATE_VALID;
					}
				}

#ifdef TEST
				std::cout << std::endl << "It has strongly_similar result."
					<< std::endl;
				std::cout << "Canonical ID = " << canonical_id << std::endl
					<< std::endl;
#endif
				break;
			}
		}
		if (has_canonical_result)
			break;
	}

	//Weakly similar
	if (has_canonical_result) {
		for (k = 0; k < ap_result_vec.size(); k++) {
			if (k == i || k == j)
				continue;
			stats.nweak_compare++;
			retval = weakly_similar(ap_result_vec[canonical_index].pulses,
						ap_result_vec[k].pulses);
			if (retval) {
				stats.nweak++;
				//set the validate state..
				ap_result_vec[k].validate_state = VALIDATE_STATE_VALID;
				for (m = 0; m < result_vec.size(); m++) {
					if (result_vec[m].id == ap_result_vec[k].result_id){
						//std::cout << "[RESULT#" << result_vec[m].id << "]# VALID (WEALKLY SIMILAR)" << std::endl;
						result_vec[m].validate_state = VALIDATE_STATE_VALID;
					}
				}
			} else {
				log_messages.printf(SCHED_MSG_LOG::MSG_DEBUG,
								"AP_CHECKSET: ap_signals are NOT weakly similar\n");

				//set the validate state..
				ap_result_vec[k].validate_state = VALIDATE_STATE_INVALID;
				for (m = 0; m < result_vec.size(); m++) {
					if (result_vec[m].id == ap_result_vec[k].result_id){
						//std::cout << "[RESULT#" << result_vec[m].id << "]# INVALID (NON WEALKLY SIMILAR)" << std::endl;
						result_vec[m].validate_state = VALIDATE_STATE_INVALID;
					}
				}
			}
		}
	}

	//granted_credit
	for (k = 0; k < ap_result_vec.size(); k++) {
		//fine maximum-minimum credit and its index.
		if (ap_result_vec[k].validate_state == VALIDATE_STATE_VALID) {
			std::cout << "result id : " << ap_result_vec[k].result_id << std::endl;

			if (ap_result_vec[k].claimed_credit < 0) {
				ap_result_vec[k].claimed_credit = 0;
			}
			if (nvalid == 0) {
				max_credit = min_credit = ap_result_vec[k].claimed_credit;
				max_credit_i = min_credit_i = k;
			} else {
				if (ap_result_vec[k].claimed_credit >= max_credit) {
					max_credit = ap_result_vec[k].claimed_credit;
					max_credit_i = k;
				}
				if (ap_result_vec[k].claimed_credit <= min_credit) {
					min_credit = ap_result_vec[k].claimed_credit;
					min_credit_i = k;
				}

				std::cout << "min : " << min_credit << std::endl;
				std::cout << "max : " << max_credit << std::endl;
			}
			nvalid++;
		}
	}

	if (nvalid == 2) {
		granted_credit = min_credit;
	} else {
		// Take care of case where all claimed credits are equal.
		if (max_credit == min_credit) {
			granted_credit = min_credit;
		} else {
			for (k = 0; k < ap_result_vec.size(); k++) {
				if (!ap_result_vec[k].has_pulse) continue;
				if (ap_result_vec[k].validate_state != VALIDATE_STATE_VALID) continue;
				if (k == max_credit_i) continue;
				if (k == min_credit_i) continue;
				sum += ap_result_vec[k].claimed_credit;
			}

			granted_credit = sum / (nvalid - 2);
		}
	}

#ifdef TEST
	std::cout << "sum = " << sum << std::endl;
	std::cout << "nvalid := " << nvalid << std::endl;
	std::cout << "granted_credit is " << granted_credit << std::endl;
#endif

		for (m = 0; m < result_vec.size(); m++) {
			if (result_vec[m].validate_state == VALIDATE_STATE_VALID) {
				result_vec[m].granted_credit = granted_credit;
				//std::cout << "[CREDIT FROM CHECK_SET#" << result_vec[m].id << "] " << result_vec[m].granted_credit << std::endl;
			}
		}
}

ap_checkset::ap_checkset(vector< RESULT>& result_vec, WORKUNIT& wu)
	: track_mem<ap_checkset>("ap_checkset"), canonical_id(0), granted_credit(0), has_canonical_result(false),
	retry(true)
{
	try {
		init(result_vec, wu.min_quorum);
		int i;
		/*for ( i = 0; i < result_vec.size(); i++){
			std::cout << "[VALIDATE STATE FROM CHECK_SET#" << result_vec[i].id << "] " << result_vec[i].validate_state << std::endl;
		}*/
	} catch (ValidatorError &v_err) {
#ifdef TEST
		std::cout << "Re-throw ValidatorError()." << std::endl;
		std::cout << v_err.what() << std::endl;
#endif
		throw;
	}
}

/*/////////////////////////////////////////////////////////////////////
//    ap_checkpair class					  //
/////////////////////////////////////////////////////////////////////*/

int ap_checkpair::init(RESULT& result_new, RESULT const & result_conanical)
{
	ResultFile ap_canonical, ap_new;
	try {
		ap_new = ResultFile(result_new);
	} catch (ResultFileError &rerr) {
		retry = true;
		result_new.outcome = RESULT_OUTCOME_VALIDATE_ERROR;
		result_new.validate_state = VALIDATE_STATE_INVALID;
		log_messages.printf(SCHED_MSG_LOG::MSG_CRITICAL,
						"[CHECK_PAIR:RESULT#%d] %s\n", result_new.id,
						rerr.what().c_str());
		throw ValidatorError("Couldn't create new AP_RESULT object.");
	}

	try {
		ap_canonical = ResultFile((RESULT &) result_conanical);
	} catch (ResultFileError &rerr) {
		retry = true;
		throw ValidatorError("Couldn't create canonical AP_RESULT object.");
	}

	if (weakly_similar(ap_canonical.pulses, ap_new.pulses)) {
		result_new.validate_state = VALIDATE_STATE_VALID;
		result_new.granted_credit = result_conanical.granted_credit;
		has_weakly_similar = true;
		retry = false;
	} else {
		result_new.validate_state = VALIDATE_STATE_INVALID;
		has_weakly_similar = false;
	}
}

ap_checkpair::ap_checkpair(RESULT& result_new, RESULT const & result_conanical)
	: track_mem<ap_checkpair>("ap_checkpair"), retry(true), has_weakly_similar(false)
{
	try {
		init(result_new, result_conanical);
	} catch (ValidatorError &v_err) {
		log_messages.printf(SCHED_MSG_LOG::MSG_CRITICAL,
						"[CHECK_PAIR:RESULT# canonical:%d #%d] %s\n", result_conanical.id,
						result_new.id, v_err.what().c_str());
		retry = true;
		throw;
	}
}

