#include "traffic_agent.cpp"
const string testMessage = "test_message";
const double measurement_cycle_time = 90; // s
const double saturated_stream = 0.5; // car/s
const int sample_cycles = 5;


class SignalGroup {
public:
	string name;
	Array <Lane*> lanes;
	double green_time_ratio;
	int average_queue_length;
	string queue_name;
	int cycle_counter;

	SignalGroup () {
		average_queue_length = 0;
	}

	void adapt() {
		if ( (cycle_counter +1)% sample_cycles == 0 )
			average_queue_length = 0;
	}

	
	int getSumCarsWaitingTime ( double time_cycle ) {
		int q = average_queue_length/sample_cycles; // pomocna delka fronty - UPRAVIT - pocitat s vetsi nez prumernou?
		int cars_per_green = time_cycle * green_time_ratio * lanes.length() * saturated_stream; // pocet aut ktere projedou na zelenou
		int sum_cars_waiting_time = 0; // suma aut * cekacich casu
		int i = 0; // iterator
		while ( q > cars_per_green ) {
			q -= cars_per_green;
			sum_cars_waiting_time += q*time_cycle;
			i ++;
		}
		// + zbytek fronty + prvni nezapocitana vlna
		if ( i > 0 ) {
			sum_cars_waiting_time += q*time_cycle + cars_per_green*(1.0-green_time_ratio)*time_cycle*0,5;
			//cout << "delsi fronta " << q << " ";
		}

		// pocet aut * stredni hodnota cekaci doby pri volne krizovatces
		else {
			sum_cars_waiting_time += (0.5*(1.0-green_time_ratio))*(time_cycle*q);
			//cout << "kratsi fronta " << q << " ";
		}

		//cout << "signal group " << name << " tc " << time_cycle << " cekani " << sum_cars_waiting_time << " fronta " << average_queue_length << endl;
		
		return sum_cars_waiting_time;
	}

	void addQueue( int q ) {
		
		if ( q > 0 )
			average_queue_length += q;
	}	

	
};

class TrafficAgentCycleTime : public BaseTrafficAgent {
public:
	int Tc;
	int minTc;
	int maxTc;
	int stepTc;
	int idealTc;
	static const int d_Tc = 3; // pocet navrhovanych delek cyklu	
	int cycle_counter;
	int max_profit;
	unsigned int n_of_broadcast;
	Array <int> lane_sum;
	Array <SignalGroup*> signal_groups;
	Array <int> received_Tcs;
	Array <int> received_profit_sum;

// POMOCNE FUNKCE

	void printVector ( RV rv_vector, vec vector, string description ) {
		cout << endl << description << " " << name << endl;
		int k = 0;
		for ( int i = 0; i < rv_vector.length(); i ++ ) {
			cout << rv_vector.name(i) << " : ";
			for ( int j = 0; j < rv_vector.size(i); j ++ ) {				
				cout << vector(k) << " ";
				k ++;
			}
			cout << endl;
		}
		cout << endl;
	}

	void echo ( string message ) {
		cout << name << " hlasi: " << message << endl;		
	}

	unsigned int find_index ( RV rv_vector, string index_string ) {
		for ( unsigned int i = 0; i < (int) rv_vector.length(); i ++) {
			if ( rv_vector.name(i) == index_string )
				return i;
		}
		return rv_vector.length();
	}

	unsigned int find_index ( Array <string> arr, string index_string ) {
		for ( unsigned int i = 0; i < (int) arr.length(); i ++) {
			if ( arr(i) == index_string )
				return i;
		}
		return arr.length();
	}

// VYPOCETNI FUNKCE

	int getSumCarsWaitingTime ( double time_cycle ) {
		int sum = 0;
		SignalGroup * sg;
		for ( int i = 0; i < signal_groups.length(); i ++ ) {
			sg = signal_groups(i);
			sum += sg->getSumCarsWaitingTime( time_cycle );
		}
		return sum;
	}

	int getQueue () {
		int queue = 0;
		SignalGroup *sg;
		for ( int i = 0; i < signal_groups.length(); i ++ ) {
			sg = signal_groups(i);
			queue += sg->average_queue_length;
		}
		return queue;
	}

	// NEPOUZIVAT?
	//int getIdealTc() {
	//	int min_waiting_time = getSumCarsWaitingTime(Tc);
	//	int waiting_time;
	//	int idealTc = Tc;
	//	cout << name << " Tc wt" << endl;
	//	for ( int t_c = Tc - 2*stepTc; t_c <= Tc + 2*stepTc; t_c += stepTc ) {
	//		waiting_time = getSumCarsWaitingTime(t_c);
	//		cout << t_c << " " << waiting_time << endl;
	//			if ( waiting_time < min_waiting_time ) {
	//				min_waiting_time = waiting_time;
	//				idealTc = t_c;
	//			}
	//	}
	//	cout << "IDEAL " << idealTc << endl;
	//	return idealTc;
	//}

	int getIdealTc() {
			
	}


	void send2neighbour( Setting &set, int i, string messageName, double messageValue ) {		
		if ( i < neighbours.length() ) {
			Setting &msg =set.add(Setting::TypeGroup);
			UI::save( neighbours(i), msg, "to" );
			UI::save (name,msg,"from");
			UI::save( messageName, msg, "what" );
			UI::save( messageValue, msg, "value" );
		}
		else {
			//throw new Exception("soused "+((string)i)+" neexistuje");
			//cout << endl << endl << "soused " << i << mimo
			std::stringstream out;
			out << "soused " << i << " neexistuje";
			//throw out.str();
			cout << out.str();
		}
	}

	void send2allNeighbours ( Setting &set, string messageName, double messageValue ) {
		for ( int i = 0; i < neighbours.length(); i++ ) {
			send2neighbour( set, i, messageName, messageValue );
		}
	}

	int getProfit( int time_cycle ) {
		if ( Tc != time_cycle )
			return getSumCarsWaitingTime( Tc ) - getSumCarsWaitingTime ( time_cycle );
		else
			return 0;
	}

	
// FUNKCE VOLANE V main_loop NA ZACATKU
	void from_setting( Setting &set ) {
		BaseTrafficAgent::from_setting(set);
		//TrafficAgentCycleTime::Tc = 80;
	}

	void validate (){
		rv_action = RV("Tc",1);
		rv_action.add( RV( stage_names, ones_i(stage_names.length()) ) );
		Tc = 80;
		minTc = 60;
		maxTc = 120;
		stepTc = 5;
		max_profit = 0;
		received_profit_sum.set_length( 2*d_Tc +1 );
		received_Tcs.set_length( 2*d_Tc +1 );
		cycle_counter = 0;

		BaseTrafficAgent::validate();
		// inicializace signalnich skupin
		for ( int i = 0; i < lanes.length(); i ++ ) {
			Lane * l = & lanes(i);
			int sg_index = -1;
			for ( int j = 0; j < signal_groups.length(); j ++ ) {
				SignalGroup * sg;
				sg = signal_groups(j);
				if ( sg->name == l->sg )
					sg_index = j;
			}
			SignalGroup * sg;
			if ( sg_index >= 0 ) {				
				sg = signal_groups(sg_index);
			}
			else {
				sg = new SignalGroup();
				sg->name = l->sg;
				sg->average_queue_length = 0;
				signal_groups.set_size( signal_groups.length()+1, true );
				signal_groups(signal_groups.length()-1) = sg;
			}			
			sg->lanes.set_size(sg->lanes.length()+1, true);
			sg->lanes(sg->lanes.length()-1) = l;
		}
	}

// FUNKCE VOLANE V main_loop V KAZDEM CYKLU

	void adapt (const vec &glob_dt) {
		// inicializes vars of cycle of broadcast
		n_of_broadcast = 0;
		max_profit = 0;
		idealTc = Tc;
		
		for ( int i = 0; i < signal_groups.length(); i ++ ) {
			signal_groups(i)->adapt();
		}

		for ( int i = 0; i < received_profit_sum.length(); i++ ) {
			received_profit_sum(i) = 0;
		}

		// nacteni dat do signalnich skupin
		cout << endl << cycle_counter << " SG " << (cycle_counter%sample_cycles +1) << endl;
		for ( int i = 0; i < rv_queues.length(); i ++ ) {
			SignalGroup * sg;
			sg = signal_groups(i);
			sg->queue_name = rv_queues.name(i);
			sg->addQueue(queues(i));
			sg->green_time_ratio = green_times(i);
			sg->cycle_counter = cycle_counter;
			//cout << sg->name << " " << sg->getSumCarsWaitingTime(Tc) << " " << sg->average_queue_length << " " << sg->green_time_ratio*Tc << " " << sg->getTime2zeroQue() << endl;
			cout << sg->name << " " << queues(i) << " " << sg->average_queue_length << " " << sg->average_queue_length / (cycle_counter%sample_cycles +1) << endl;
		}

		//suggestedTc = idealTc = getIdealTc();		
		
		//cout << endl << "CELKOVA FRONTA " << name << " " << getQueue() << endl;

		BaseTrafficAgent::adapt(glob_dt);
	}

	void broadcast(Setting &set){		
		
		// 1. cycle of communication
		// sends all tcs in range +- d_tc and expected profits according to tc
		// format: tc_index, profit_index
		if ( n_of_broadcast == 0 && ( cycle_counter % sample_cycles == 0 ) ) {
			for ( int i = -d_Tc; i <= d_Tc; i++ ) {				
				int time_cycle = Tc + i*stepTc;
				int profit = getProfit(time_cycle);

				// send tc
				stringstream time_cycle_stream;
				time_cycle_stream << "tc_" << (i+d_Tc);
				send2allNeighbours( set, time_cycle_stream.str(), time_cycle);
				
				// send profit
				stringstream profit_stream;
				profit_stream << "profit_" << (i+d_Tc);
				send2allNeighbours( set, profit_stream.str(), profit );

				//cout << name << " tc " << time_cycle << " profit " << profit << " cekani " << getSumCarsWaitingTime(time_cycle) << endl;
			}
			//cout << endl;
		}
		
		n_of_broadcast ++;
	}

	void receive(const Setting& msg){
		string what;
		string from;
		double val;		
		UI::get(what, msg, "what", UI::compulsory);
		UI::get(from, msg, "from", UI::compulsory);
		UI::get(val, msg, "value");
		
		//cout << name << " receiving from " << from << " " << what << " : " << val<<endl;
		if ( n_of_broadcast == 1 ) {
			if ( what.substr(0,2) == "tc" ) {			
				istringstream tc_i( what.substr(3, what.length()-3) );
				int index;
				tc_i >> index;
				received_Tcs( index ) = val;
				//cout << "tc " << val << endl;
			}
			if ( what.substr(0,6) == "profit" ) {				
				istringstream profiti( what.substr(7, what.length()-7) );
				int index;
				profiti >> index;
				received_profit_sum( index ) = received_profit_sum( index ) + val;
				//cout << "profit " << val << endl;			
			}
		}
		
		if ( what == "new_stable_state" ) {
			//echo("new_stable_state");
			BaseTrafficAgent::receive(msg);		
		}	
		
	}

	void act (vec &glob_ut){
		if ( cycle_counter % sample_cycles == 0 ) {
			// choose tc with max profit		
			for ( int i = 0; i < received_profit_sum.length(); i ++ ) {
				int time_cycle = received_Tcs(i);
				int profit = received_profit_sum(i) + getProfit(time_cycle);
				if ( profit > max_profit ) {
					max_profit = profit;
					idealTc = time_cycle;
					//cout << name << " idealni Tc " << time_cycle << " s celkovym ziskem " << profit << endl;
				}
			}
			//cout << endl << name << " nastevuje TC na " << idealTc << endl;
			Tc = idealTc;


			// nastaveni delky cyklu na Tc
			vec action;
			action.set_size(rv_action._dsize());	
			int st;
			int stage_time_sum = 0;  // soucet delky fazi
			action(find_index(rv_action, "Tc")) = Tc;		
			for ( int i =0; i < stage_names.length(); i ++) {			
				if ( (i+1) < stage_names.length() ) {
					st = (stage_times(i)/80)*Tc;
					stage_time_sum += st;
					action(find_index(rv_action, stage_names(i))) = st;
				}
				else // dopocitani posledni faze - oprava zaokrouhlovaci chyby
					action(find_index(rv_action, stage_names(i))) = Tc - stage_time_sum;
			}
			action2ds.filldown(action,glob_ut);
		}
		cycle_counter ++;
	}
	

};
UIREGISTER(TrafficAgentCycleTime);