#include "WPManager.hxx"
#include "DPRSim.hxx"
#include "CodeModule.hxx"
//include relevant code module headers here
#include "CodeModules/RandomValue.hxx"
#include "CodeModules/GradientField.hxx"

WPManager::WPManager(string input) {
	wp_master = parseWP(input);
	cout << "DPRSim: WATCHPOINT ACTIVE, size:" << wp_master->c_list->names.size() << ", variables:";
	//get list of variables needed
	wpNames = wp_master->watchedVariables();
	copy( wpNames.begin(), wpNames.end(), ostream_iterator<string>( cout, " " ) );
	cout << endl;
	max_temporal = 0;
	steps = 0;
	//determine temporal extent for each variable, and full-catom-state
	for (set<string>::const_iterator i = wpNames.begin(); i != wpNames.end(); i++) {
		minExtent[*i] = wp_master->minExtent(*i);
		maxExtent[*i] = wp_master->maxExtent(*i);
		if (maxExtent[*i] > max_temporal) max_temporal = maxExtent[*i];
	}
	//set up master state save structure (map<string,map<CatomSim*,vector<int> > >)
	for (set<string>::const_iterator i = wpNames.begin(); i != wpNames.end(); i++) {
		//savedState[*i] = map<catomID,vector<int> >();
		for (hash_map<const unsigned long, CatomSim *, hash<const unsigned long>, equl>::iterator j = worldPtr->catomHash.begin();
		 			j != worldPtr->catomHash.end(); j++) {
			savedState[*i][(*j).second] = deque<int>();
		}
	}
}

WPManager::~WPManager() {
	delete wp_master;
}

void WPManager::step() {
	cout << "wp step begin" << endl;
	worldPtr->clearLines();
	//save state of each catom to statebuffers
	unsigned maxVSize = 1;
	for (set<string>::const_iterator i = wpNames.begin(); i != wpNames.end(); i++) {
		unsigned vSize = maxExtent[*i] - minExtent[*i] + 1;
		if (vSize > maxVSize) maxVSize = vSize;
		for (hash_map<const unsigned long, CatomSim *, hash<const unsigned long>, equl>::iterator j = worldPtr->catomHash.begin();
		 			j != worldPtr->catomHash.end(); j++) {
							//get value of i for catom j
							savedState[*i][(*j).second].push_front(getState((*j).second,(*i)));
							//trim vector to correct length if too long
							while (savedState[*i][(*j).second].size() > vSize) savedState[*i][(*j).second].pop_back();
		}
	}
	//MDR-TODO:store full state of every catom (if needed)
	if (max_temporal > 0) {
	
	}
	
	//don't try to match if we don't have enough state
	if (++steps < maxVSize) return;
	
	//cout << "wp match begin" << endl;
	
	//build master neighbors hash
	map<CatomSim*,set <CatomSim *> > neighbors;
	for (hash_map<const unsigned long, CatomSim *, hash<const unsigned long>, equl>::iterator j = worldPtr->catomHash.begin();
	 	j != worldPtr->catomHash.end(); j++) {
			neighbors[j->second] = set<CatomSim*>();
			int idx = 0;
			catomID neighborId = j->second->C.getNthNeighbor(idx);
			while (neighborId) {
				idx++;
				CatomSim *currNeighbor = worldPtr->catomHash[neighborId];
				neighbors[j->second].insert(currNeighbor);
				neighborId = j->second->C.getNthNeighbor(idx);
			}
		}
		
	set<Watchpoint*> activeMatchers;
	//instantiate and propagate values for 1 new matcher/catom
	for (hash_map<const unsigned long, CatomSim *, hash<const unsigned long>, equl>::iterator j = worldPtr->catomHash.begin();
	 			j != worldPtr->catomHash.end(); j++) {
			Watchpoint *newWP = wp_master->clone();
			newWP->propagate(newWP->nextSlot(),(*j).second,this);
			activeMatchers.insert(newWP);
	}
	int matches = 0;
	while (activeMatchers.size()) {
		//cout << "wp match iter:" << activeMatchers.size() << endl;
		//for each matcher...
		set<Watchpoint*> tempSet;
		for (set<Watchpoint*>::const_iterator i = activeMatchers.begin(); i != activeMatchers.end(); i++) {
			//check for termination/success
			TriState sat = (*i)->isSatisfied();
			switch (sat) {
				case yes:
					//cout << "DPRSim:WATCHPOINT MATCHED!" << endl;
					matches++;
					this->addLinesForWatchpoint(*i);
					delete (*i);
					break;
				case no:
					delete (*i);
					break;
				case maybe:
					//if not terminated, get uniqued list of possible neighbors,spread,and propagate values
					if ((*i)->isFull()) 
						cout << "DPRSim:watchpoint full, but not verified. Uncool." << endl;
					else {
						set<CatomSim*> possible = possibleNeighbors(*i,neighbors);
						//cout << possible.size() << " new matchers" << endl;
						for (set<CatomSim*>::const_iterator j = possible.begin(); j != possible.end(); j++) {
							Watchpoint *newWP = (*i)->clone();
							newWP->propagate(newWP->nextSlot(),(*j),this);
							tempSet.insert(newWP);
						}
					}
					delete (*i);
					break;
			}			
		}
		swap(tempSet,activeMatchers);	
	}
	cout << "wp step end, " << matches << " matches" << endl;
}

int WPManager::getState(CatomSim *c,string varname) {
	if (varname == "id") {
		return c->C.getID();
	}
	else if (varname == "RandomValue_value") {
		return REMOTE_CODE_MODULE(c->C.getID(),RandomValue)->value;
	}
	else if (varname == "GradientField_valuePlusOne") {
		return REMOTE_CODE_MODULE(c->C.getID(),GradientField)->value + 1;
	}
	else if (varname == "GradientField_value") {
		return REMOTE_CODE_MODULE(c->C.getID(),GradientField)->value;
	}
	cout << "no such variable:" << varname << endl;
	return 0;
}

int WPManager::lookupSavedState(CatomSim *c,string varname,int offset) {
	return savedState[varname][c][max_temporal - offset];
}

set<CatomSim*> WPManager::possibleNeighbors(Watchpoint*wp,map<CatomSim*,set <CatomSim *> > &n) {
	//get set of all used catom's neighbors from wp
	set<CatomSim *>candidates;
	for (unsigned i = 0; i < wp->c_list->names.size(); i++) {
		if (wp->c_list->names[i].second) {
			set <CatomSim *> &nSet = n[wp->c_list->names[i].second];
			candidates.insert(nSet.begin(),nSet.end());
		}
	}
	//remove the catoms already in the wp
	for (unsigned i = 0; i < wp->c_list->names.size(); i++) {
		if (wp->c_list->names[i].second) {
			candidates.erase(wp->c_list->names[i].second);
		}
	}
	set<CatomSim*> passed;
	//try each candidate, and add those that passed to the set
	for (set<CatomSim *>::iterator j = candidates.begin();j != candidates.end(); j++) {
		if (wp->isValidCandidate(n,wp->c_list->names,*j,wp->nextSlot())) //TODO: this is broken
			passed.insert(*j);
	}
	return passed;
}

void WPManager::addLinesForWatchpoint(Watchpoint *wp) {
	int r = rand() % 255;
	int g = rand() % 255;
	int b = rand() % 255;
	catomID prevID = 0;
	catomID currID = 0;
	for (vector<pair<string,CatomSim*> >::iterator i = wp->c_list->names.begin();
				i != wp->c_list->names.end(); i++) {
					if (i->second) {
						prevID = currID;
						currID = i->second->C.getID();
						if (prevID && currID) worldPtr->addLine(prevID,currID,r,g,b);
					}
	}
}
