#include<tpda2.h>
#include"timePushDown.h"
#include"treeBitOperations.h"
#include<iostream>
#include<sstream>


using namespace std;

// construction of state with given trans and tsm values
stpdastate* atomicstpda(char d1, char d2, char w1, char w2, char gc){

	stpdastate *vs = new stpdastate();
	
	vs->d1 = d1;
	vs->d2 = d2;
	
	vs->w1 = w1;
	vs->w2 = w2;
	
	vs->gc = gc;
	
	return vs;
}


// shuffle of two states
stpdastate* stpdastate::shuffle(stpdastate *s2) {

	// transion at L of s2 should be same as transion at R of this state, tsm values as well better be equal
	if( d2 != (s2->d1) || w2 != (s2->w1) )
		return nullptr;
		
	
		
	// there should be a push at L and pop at R of state s2
	if( !isPush(s2->d1) || !isPop(s2->d2) )
		return nullptr;
		
	
	/*	******** THIS IS A HUGE MISTAKE , IN OTHER FILES ALSO *****************, REMOVE THIS
	// push and pop symbol must be same
	if( (transitions[s2->d1].ps) != (transitions[s2->d2].pp) )
		return nullptr;
	*/
		
	//cout << "ilias" << endl;
		
	// push-pop edge between L and R of s2 should be added
	if( ( (s2->gc) & 1 ) == 0 ) // push-pop edge not added yet
		return nullptr;
		
	char gcn; // flag for the new state
	
	// if distance between two end points is accurate in both the states
	if( (gc & 2) && ( (s2->gc) & 2 ) ) {
		short d13 = mod(w2-w1, M) + mod(s2->w2 - s2->w1, M);
		if(d13 < M)
			gcn = (gc & 1) | ( (s2->gc) & 128) | 2;
		else
			gcn = (gc & 1) | ( (s2->gc) & 128);
			
	}
	
	else
		gcn = (gc & 1) | ( (s2->gc) & 128);
		
	return	atomicstpda(d1, s2->d2, w1, s2->w2, gcn);
}



// given trans 0 or a push trans d1(with tsm w1), find the next possible trans d2(with tsm w2)
vector<stpdastate*> getAtomicsSTPDA(char d1, char w1) {
	vector<stpdastate*> v;//vector contains states by adding a upcoming transition to right side
	
	char q = transitions[d1].target; // target state of trans d4
	
	char d2, w2; // next trans and tsm value
	char gcn; // new flag variable for state
	
	int lb, ub; // pop bounds
	int lbc, ubc; // clock bounds
	
	bool checkflag; // true : there is a check at d2
	bool popflag; // true : there is a pop at d2
	
	// iterate through all the upcoming transitions
	for(char i=0; i < nexttrans[q].size(); i++) {
		d2 = nexttrans[q][i]; // i-th upcoming transition
		
		lb = transitions[d2].lbs[0]; // lower bound for stack
		ub = transitions[d2].ubs[0]; // upper bound for stack 
		
		lbc = transitions[d2].lbs[1]; // lower bound for clock
		ubc = transitions[d2].ubs[1]; // upper bound for clock
		
		checkflag = isChecked(1, d2); // true : there is a check at d2
		popflag = isPop(d2); // true : there is a pop at d2
		
		// note  : distance between d1 and d2 is always accurate
		if(popflag) {
			if( isPush(d2) )
				gcn = 1 | 2;	// push edge added, but pop edge not added(add before a push)
			else
				gcn = 1 | 128 | 2; // push and pop both edges added
		}	

		else
			gcn = 2;
		
		// if there is a pop at d2, there must a push at d1
		if( popflag && !isPush(d1) ) { }
		
		// push symbol at d1 must be same as pop symbol at d2
		else if( popflag && ( transitions[d1].ps != transitions[d2].pp) ) { }

		
		else{
			short int d12;
			for(w2 = 0; w2 < M; w2++) {
				d12 = mod(w2-w1, M);
				if( ( !checkflag || (d12 >= lbc && d12 <= ubc) ) && ( !popflag || (d12 >= lb && d12 <= ub) ) ){
					v.push_back( atomicstpda(d1, d2, w1, w2, gcn) );
				}
			}
		}
	}
	return v;
}


/*Add the next transition to the current state*/
/*if there is a push at R, then don't call here*******/
vector<stpdastate*> stpdastate::addNextTPDA() {
	vector<stpdastate*> v; // set of states generated by adding a new transition to the right side
	
	if( isPush(d2) ) // there should not be any push at R
		return v; // return empty vector
	
	/*	// In new technique, pop and push can be at the same time, this check is not needed
	if( isPop(d2) && ( gc & 128 ) == 0 ) // there is a pop at d2, but pop edge not added yet
		return v;
	*/
		
	// if there is a push at L and there is a pop at R, then first shuffle, then add new transitions
	if( isPush(d1) && isPop(d2) &&  (gc & 1) )
		return v;
	
	char d3, w3; // transion and tsm at newly added rightmost point
	char gc2, gcn; // gc2 : temp flag, gcn : new state flag

	bool checkflag;
	bool popflag;
	
	int lbc, ubc; // clock constraint's bound
	int lb, ub; // lower and upper bound for stack
	short d13, d23;

	char q = transitions[d2].target; // target state of trans d4
	
	// iterate through all the upcoming transitions
	for(char i=0; i < nexttrans[q].size(); i++) {
	
		d3 = nexttrans[q][i]; // i-th upcoming transition
		
		lb = transitions[d3].lbs[0]; // lower bound for stack
		ub = transitions[d3].ubs[0]; // upper bound for stack 
		
		lbc = transitions[d3].lbs[1]; // lower bound for clock
		ubc = transitions[d3].ubs[1]; // upper bound for clock
		
		checkflag = isChecked(1, d3);
		popflag = isPop(d3);
		
		if( popflag ) { // push pop both are being done
			if( isPush(d3) )
				gc2 = 1;
			else
				gc2 = 1 | 128;
		}
		else // push info of previous state
			gc2 = (gc & 1);
		
		// if there is a pop at d5, but no push at d2
		if( popflag && !isPush(d1) ) { }
		
		// if there is a pop at d3, but  push is already done at d1
		else if( popflag && (gc & 1) ) { }
		
		// push-pop symbol is not same
		else if( popflag && ( transitions[d1].ps != transitions[d3].pp) ) { }
		
		// o.w add new transtitions to the right
		else{
			if(gc & 2) {
				for(w3 = 0; w3 < M; w3++) {
					d23 = mod(w3-w2, M);
					d13 = mod(w2-w1, M) + d23;
					if(d13 < M)
						gcn = gc2 | 2;
					else
						gcn = gc2;
						
					if( ( !checkflag || ( d23 >= lbc && d23 <= ubc ) ) && ( !popflag || ( d13 >= lb && d13 <= ub ) ) )
						v.push_back( atomicstpda(d1, d3, w1, w3, gcn) );
				}
			}
			
			else if(ub == INF) {
				gcn = gc2;
				for(w3 = 0; w3 < M; w3++) {
					d23 = mod(w3-w2, M);
					if( !checkflag || (d23 >= lbc && d23 <= ubc) )
						v.push_back( atomicstpda(d1, d3, w1, w3, gcn) );
				}
				
			}
		}
	}
	
	return v;
}


set<string> stpdatrie;

bool identity(stpdastate *vs) {
	ostringstream os; // output stream
	
	os << (vs->d1) << (vs->d2);
	os << (vs->w1) << (vs->w2);
	os << (vs->gc);

	// return true if os.str() inserted in the set 'strie' successfully 
	return (stpdatrie.insert(os.str())).second; 
}

// print a state of tree automation
void stpdastate::print() {
	cout << "\nAbstarct tree automation state:\n";
	
	cout << "\tTransitions : " << int(d1) << ",  " << int(d2) << endl;
	
	cout << "\tTSM : " << int(w1) << ",  " << int(w2) << endl;
	
	cout << "\tPush edge added to L : ";
	if(gc & 1)
		cout << 1 << endl;
	else
		cout << 0 << endl;
		
	cout << "\tPop edge added to R : ";
	if(gc & 128)
		cout << 1 << endl;
	else
		cout << 0 << endl;
		
	cout << "\tpush at L : ";
	if(isPush(d1))
		cout << int(transitions[d1].ps) << endl;
	else
		cout << endl;
		
	cout << "\tpop at L : ";
	if(isPop(d1))
		cout << int(transitions[d1].pp) << endl;
	else
		cout << endl;
		
	cout << "\tpush at R : ";
	if(isPush(d2))
		cout << int(transitions[d2].ps) << endl;
	else
		cout << endl;
		
	cout << "\tpop at R : ";
	if(isPop(d2))
		cout << int(transitions[d2].pp) << endl;
	else
		cout << endl;
	
	cout << "\tAccuracy : " << bool(gc & 2) << endl;
	
	cout << "\tFlag : " << bool(gc & 128) <<bool(gc & 64) <<bool(gc & 32) <<bool(gc & 16) <<bool(gc & 8) <<bool(gc & 4) <<bool(gc & 2) <<bool(gc & 1) <<endl; 
	cout << endl;
}


// return true : if current state is final
bool stpdastate::isFinal() {
		
	if(isPush(d2))
		return false;
	
	// this below test can make sure that in the last transition there is no push along with the pop
	if( isPop(d2) &&  (gc & 128) == 0) // there is a pop at d4, but not added yet
		return false;
		
	// 0 is initial state for a tpda
	if( transitions[d1].source != 0) // remember 0  is the new initial state
		return false;
		
	if(w1 != 0) // first time stamp must be 0
		return false;
		
	// target state of the transiton at point R should be a final state
	if( transitions[d2].target != SF) 
		return false;
		
	return true;
}


// given a atomic tree automation state give a run of the TPDA
stpda_run* getatomicrunstpda(stpdastate* vs) {
	stpda_run *rs = new stpda_run();

	rs->P = 2;
	rs->del = new char[2];
	rs->w = new char[2];
	
	rs->del[0] = vs->d1;
	rs->del[1] = vs->d2;
	
	rs->w[0] = vs->w1;
	rs->w[1] = vs->w2;
	
	return rs;
}


// GIVEN the current run, append the trans d5 with tsm w5
stpda_run* stpda_run::add_next(char d3, char w3) {
	stpda_run *pr = new stpda_run();
	
	pr->P =  P + 1;
	pr->del = new char[pr->P];
	pr->w = new char[pr->P];
	
	for(char i=0; i < P; i++) {
		pr->del[i] = del[i];
		pr->w[i] = w[i];
	}
	
	pr->del[P] = d3;
	pr->w[P] = w3;
	
	return pr;
}


// shuffle two runs
stpda_run* stpda_run::shuffle(stpda_run *s2) {
	stpda_run *pr = new stpda_run();
	
	pr->P =  P + (s2->P) - 1;
	
	pr->del = new char[pr->P];
	pr->w = new char[pr->P];
	
	char i, j;
	
	for(i=0, j=0; i < (P-1); i++, j++) {
		pr->w[j] = w[i];
		pr->del[j] = del[i];
	}
	
	for(i=0; i < (s2->P); i++, j++) {
		pr->w[j] = s2->w[i];
		pr->del[j] = s2->del[i];
	}
	
	return pr;
}


// print a run of the given TPDA
void stpda_run::print() {
	short int lt = 0, ct=0; // lt : last timestamps, ct : current time stamps
	cout << endl << "A run of the automation as a proof that the language is non-empty, the run given as a sequence of pairs (transition, time stamp) : " << endl;
//cout << "(0, 0.0) ";
	for(char i=0; i < P; i++) {
	
		// this two line changes for accuracy
		if(w[i] < lt) // if current time stamps less than last time stamps
			ct = M + ct + (w[i] - lt);
		else
			ct = ct + (w[i] - lt);
			
		lt = w[i];
			
		///cout << "-> (" << int(del[i]) << ", " << int(ct)  << ".0" << ") -> " << "(" << int(transitions[del[i]].target) << ", " << int(ct) << ".0"  << " ";
		
		cout << "(" << int(del[i]) << ", " << int(ct)  << ".0" << ") ->  ";
//		if( (transitions[del[i]].target) <= 7)
//			cout << "-> (" << int(transitions[del[i]].target) << ", " << int(ct) << ".0)"  << " ";
	}
	
	cout << endl << endl;
}



// get the run of the timed system starting from i-th index state of all states 'states'
stpda_run* printRun(vector<stpdastate*> &states, vector<bp_stpda> &v, int i) {
	bp_stpda bp = v[i]; // backpropagation information for i-th index state
	
	stpda_run *rs, *rs1, *rs2;
		
	if( (bp.type) == 0) { // if the i-th state is an tpda_atomic state
		//cout << "i:" << i << "," << int(bp.type) <<  endl;
		//states[i]->print();

		return getatomicrunstpda(states[i]);
	}
	
	if( (bp.type) == 1) { // if state states[i] is obtained by forgetting color 'bp.f' from state states[bp.left]
		rs1 = printRun(states, v, bp.left);
		
		char d = bp.right;
		int x = bp.right;
		x = (x >> 8);
		char w = x; 
		stpdastate *vs = atomicstpda( rs1->del[rs1->P - 1], d, rs1->w[rs1->P - 1], w, 1 );
		rs2 = getatomicrunstpda(vs);
		rs = rs1->shuffle(rs2);
		//cout << "i:" << i << "," << int(bp.type) << "," << bp.left << "," << int(d) << "," << int(w) << endl;
		//states[i]->print();

		return rs;
	}
	
	
	
	rs1 = printRun(states, v, bp.left); // get run for the state states[bp.left]
	rs2 = printRun(states, v, bp.right);// get run for the state states[bp.right]
	rs = rs1->shuffle(rs2); // shuffle two runs rs1 and rs2 with accuracy given by bp.ac
	
	//cout << "i:" << i << "," << int(bp.type) << "," << bp.left << "," << bp.right << endl;
	//states[i]->print();

	return rs;
}



// check if a tpda of one clock is empty or not
bool isEmptySTPDA() {
	
	int N=0,count=0;
	vector<stpdastate*> states;
	vector<stpdastate*> v;
	
	stpdastate *rs, *vs;
	
	vector<bp_stpda> vrs; // vector for keeping track of connection between current states with previous states
	bp_stpda xrs; // bac
	
	stpda_run *prs, *prs1, *prs2;
	
	
	int i, t, d3, w3;
	char w;
	
	xrs.type = 0; // atomic state type
	
	
	v = getAtomicsSTPDA(0, 0);
	for(i=0; i < v.size(); i++) {
		if( identity(v[i]) ) {
			states.push_back(v[i]);
			vrs.push_back(xrs);
			N++;
			if( v[i]->isFinal() ) {
				v[i]->print();
				prs = printRun(states, vrs, N-1);
				prs->print();
				return false;
			}
		}
	}
	
	/*
	for(t=1; t <= T; t++) {
		if( isPush(t) ) {
			for(w=0; w < M; w++) {
				v = getAtomicsSTPDA(t, w);
				for(i=0; i < v.size(); i++) {
					if( identity(v[i]) ) {
						states.push_back(v[i]);
						vrs.push_back(xrs);
						N++;
						if( v[i]->isFinal() ) {
							v[i]->print();
							prs = printRun(states, vrs, N-1);
							prs->print();
							return false;
						}
					}
				}
			}	
		}
	}
	*/
	
	/*
	for(i=0; i < N; i++) {
		cout << "state " << i << ":" << endl;
		states[i]->print();
	}
	*/
	
	
	// iterate through all the states
	for(count=0; count < N; count++) {
	
		if(count %5000 == 0)
			cout << "#states" << endl;
	
		if( (count % 100) == 0 || count <= 100) // print #iterations
			cout << count << endl;
			
		rs = states[count]; // get the state at index count
		//rs->print();
		
		//***
		xrs.type = 0;
		if( isPush(rs->d2) && !pushDone[rs->d2][rs->w2] ) {
			v = getAtomicsSTPDA(rs->d2, rs->w2);
			for(i=0; i < v.size(); i++) {
				if( identity(v[i]) ) {
					states.push_back(v[i]);
					vrs.push_back(xrs);
					N++;
					if( v[i]->isFinal() ) {
						v[i]->print();
						prs = printRun(states, vrs, N-1);
						prs->print();
						return false;
					}
				}
			}
			pushDone[rs->d2][rs->w2] = true;	
		}
		//****
		
		
		xrs.type = 1; // add_stack type
		xrs.left = count;
		// try to add new trans to the state rs
		v = rs->addNextTPDA();
		for(i=0; i < v.size(); i++) {
			if( identity(v[i]) ) { // if the i-th state is new
				d3 = (v[i]->d2); w3 = (v[i]->w2);
				xrs.right = d3 + (w3 << 8);
				
				states.push_back(v[i]);
				vrs.push_back(xrs);
				N++;
				if( v[i]->isFinal() ) { // if this state is final
					v[i]->print();
					prs = printRun(states, vrs, N-1);
					prs->print();
					return false;
				}
				
			}
		}
		
		
		xrs.type = 2; // add_stack type
		
		// try to do shuffle with all the previous(as left as well as right state)
		if( isPush(rs->d2) || ( isPush(rs->d1) && isPop(rs->d2) ) ) {
			for(i=0; i <= count; i++) {
				xrs.left = count;
				xrs.right = i;
				// shuffle, considering rs as left state
				vs = rs->shuffle( states[i] );
				if(vs != nullptr) { // if new state is valid
					if( identity(vs) ) {
						states.push_back(vs);
						vrs.push_back(xrs);
						N++;
						if( vs->isFinal() ) {
							vs->print();
							prs = printRun(states, vrs, N-1);
							prs->print();
							return false;
						}		
					}
				}
				
				
				xrs.left = i;
				xrs.right = count;
				
				// shuffle, considering rs as right state
				vs = states[i]->shuffle(rs);
				
				if(vs != nullptr) { // if new state is valid
					if( identity(vs) ) {
						states.push_back(vs);
						vrs.push_back(xrs);
						N++;
						if( vs->isFinal() ) {
							vs->print();
							prs = printRun(states, vrs, N-1);
							prs->print();
							return false;
						}
						
					}
				}
			}
		}
		
	}
	
	cout << "\n\n------------------------\n" << endl;
	states[0]->shuffle(states[3]);

	return true;
}
