/*!
  \file
  \brief Traffic Light Agents 
  \author Vaclav Smidl.
*/

#ifndef TRAGE_H
#define TRAGE_H


#include <base/participants.h>
	
using namespace bdm;

class BaseTrafficAgent;

//! detector of traffic variables
class Lane{
	public:
		Array<string> inputs;
		Array<string> outputs;
		vec input_distances;
		vec output_distances;
		vec alpha; //size of outputs
		string queue;
		string sg;

		//! function loading info from Setting
		void from_setting(const Setting &set){
			UI::get(inputs,set,"inputs",UI::compulsory);
			UI::get(outputs,set,"outputs",UI::compulsory);
			UI::get(input_distances,set,"input_distances",UI::compulsory);
			UI::get(output_distances,set,"output_distances",UI::compulsory);
			UI::get(alpha,set,"alpha",UI::compulsory);
			UI::get(queue,set,"queue",UI::compulsory);
			UI::get(sg,set,"sg",UI::compulsory);
			
		}
};

//! class that operates on a signal group
class LaneHandler {
	protected:
		//! pointer to physical lane
		const Lane &lane;
		//! agent pointer
		BaseTrafficAgent *agent;
		
	public:
		//! actual data from the relevant signal group
		vec inputs;
		//!
		double queue;
		//! 
		//! description of det_data
		RV rv_inputs;
		//! description of det_data
		RV rv_outputs;
		//! description of det_data
		RV rv_queue;
		//! link from global measured data 
		datalink agentin2input;
		//!
		datalink output2agentout;
		//!
		int queue_index;

	public:
		LaneHandler(const Lane &lane0): lane(lane0){
			for (int i=0;i<lane0.inputs.length();i++){
				rv_inputs.add(RV(lane.inputs(i), 2));
			}
			for (int i=0;i<lane0.outputs.length();i++){
				rv_outputs.add(RV(lane.outputs(i), 2)); 
			}
			rv_queue.add(RV(lane.queue, 1)); 
			inputs.set_size(rv_inputs._dsize());
		}
		
		
		void connect_data(BaseTrafficAgent &agent0);

		//! computes expected density in cars/s
		double expected_density();

		//! arbitrary function that computes the need of the signal group for green light in common units (number of waiting cars?)
		double expected_output(double green_time);
};

/*!
\brief  Basic Traffic Agent

*/
class BaseTrafficAgent : public Participant {
	LOG_LEVEL(BaseTrafficAgent,logdata);
	public:
		//! Signal Groups
		Array<Lane> lanes;

		Array<LaneHandler*> lanehs;
										
		//!data from messages
		vec inputs;
		
		//! decription of msg_data
		RV rv_inputs;
		
		//! data to broadcast
		vec outputs;
		
		//! description of broadcast dataind
		RV rv_outputs;
		
		vec queues;
		
		//! description of queues
		RV rv_queues;
		
		//! datalink from DS to input variables
		datalink ds2inputs;

		//! datalink from DS to output variables
		datalink ds2queues;

		//! action description
		RV rv_action;
				
		datalink_part action2ds;
		
		Array<string> neighbours;
		
		Array<RV> rv_neighbours_out;
		
		Array<datalink> output2neighbour;

		//! simulator's step length in seconds
		int step_time;

		//! lenght of cycle in seconds
		int cycle_time;
		
	public:
		void validate(){
			
			lanehs.set_length(lanes.length());
			for (int l=0; l<lanes.length(); l++){
				lanehs(l) = new LaneHandler(lanes(l));
				
				rv_inputs.add(lanehs(l)->rv_inputs);
				rv_outputs.add(lanehs(l)->rv_outputs);
				rv_queues.add(lanehs(l)->rv_queue);
			}
			inputs.set_size(rv_inputs._dsize());
			outputs.set_size(rv_outputs._dsize());
			queues.set_size(rv_queues._dsize());
			
			for (int l=0; l<lanes.length(); l++){
				lanehs(l)->connect_data(*this);
			}
			
			//for -- rv_outputs -- 
			// TODO vybrat rv pro sousedy 
			rv_neighbours_out.set_length(neighbours.length());
			output2neighbour.set_length(neighbours.length());
			
			for (int i=0; i<neighbours.length(); i++){
				for (int r=0; r<rv_outputs.length(); r++){
					int str_pos = rv_outputs.name(r).compare(neighbours(i));
					if (str_pos>neighbours(i).length()){
						rv_neighbours_out(i).add(rv_outputs.subselect(vec_1(r)));
					}
				}
				// connect datasource
				output2neighbour(i).set_connection(rv_neighbours_out(i), rv_outputs);
			}
			
			// lanehs knows RVS
			// write internal checks if all was loaded OK
			
		}
		void receive(const Setting &msg){
			string what;
			UI::get(what, msg, "what", UI::compulsory);
			
			if (what=="new_stable_state"){ // 
				// field data 
				// extract decription of teh received datavector
				shared_ptr<RV> rv=UI::build<RV>(msg,"rv",UI::compulsory);
				// find if it is needed
				ivec ind=rv->dataind(rv_inputs); // position of rv in in_rv;
				if (ind.length()>0){ //data are interesting
					vec dt;
					UI::get(dt, msg, "value",UI::compulsory); // get data
					set_subvector(inputs, ind,  dt); //check size?
				}				
			} else {
				Participant::receive(msg);
			}
		}
		void log_register(logger &L, const string &prefix){
			root::log_register ( L, prefix );
			if ( log_level[logdata]){
				L.add_vector ( log_level, logdata, RV ( 1 ), prefix );	
			}
		}
		void log_write() const {
			if (log_level[logdata]){
				log_level.store(logdata, inputs);
			}
		}
		
		void broadcast(Setting& set){
			// broadcast data to all neighbours
			for (int i=0; i<neighbours.length(); i++){
				Setting &msg =set.add(Setting::TypeGroup);
				
				// if...
				// copy from create message
				// create msg with fields {to=..., what=data, rv=..., value = ...}
				UI::save ( neighbours(i), msg, "to");
				UI::save ( (string)"new_stable_state", msg, "what");
				UI::save ( &(rv_neighbours_out(i)), msg, "rv");
				UI::save( output2neighbour(i).pushdown(outputs), msg, "value");
			}
			
		}
		void adapt(const vec &glob_dt){
			// copy data from global vector to sSGHandlers
			ds2inputs.filldown(glob_dt, inputs);
			//copy data from neighbours
			ds2queues.filldown(glob_dt, queues);
			// copy sg_length ... and others...
		}
		void act(vec &glob_ut){
			vec action; // trivial stuff
			action2ds.filldown(action,glob_ut);
		}
		
		void ds_register(const DS &ds){
			//register ds2output
			ds2inputs.set_connection(rv_inputs, ds._drv());
			ds2queues.set_connection(rv_queues, ds._drv());
			
			inputs.set_size(rv_inputs._dsize());
			action2ds.set_connection( ds._urv(), rv_action);

		}
		
		void from_setting(const Setting &set);
};
UIREGISTER(BaseTrafficAgent);

#endif //TRAGE_H
