package traffic;

import jason.asSyntax.ASSyntax;
import jason.asSyntax.Literal;
import jason.asSyntax.Structure;
import jason.asSyntax.Term;
import jason.environment.grid.Location;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicInteger;

import java.text.DecimalFormat;

import java.io.File;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/** 
 * class CO2Environment: environment code for project CO2-jmadem.mas2j
 *
 * @author Fco. Javier Cordero Cadenas, Francisco Grimaldo
 */

public class CO2Environment extends jason.environment.TimeSteppedEnvironment {

    private Logger logger = Logger.getLogger("jasonTeamSimLocal.mas2j." + CO2Environment.class.getName());
    
    private WorldModel  model;																	 
    private WorldView   view;																	

	private int numHabitants = 0;																//number of habitants in the world
    private int sleep = 0;																		//time sleep
    private boolean running  = true;															//indicates whether the simulation is running
    private boolean hasGUI   = true;															//indicates whether the simulation has active its view
    private boolean hasLog   = false;															//indicates whether the simulation has log files
	private int	actualIter = 1;																	//actual iteration
	private int	nIter	 = 1;																	//total number of iterations to simulate
	private int habitantsXGroup = 1;															//maximum number of the habitants that forms a group
	//private Double[] habsPerVehicle = new Double[VehicleType.N_TYPES];						//counts the number of habitants travelling by vehicle type
	private HashMap<Integer,Double[]> habsPerVehicle = new HashMap<Integer,Double[]>();			//counts the number of habitants travelling by vehicle type
	private Double counterVehicles = 0.0;														//counts the number of vehicles in use
	private AtomicInteger counterEnd = new AtomicInteger(0);									//counts the number of habitants that has sent the end action
	private AtomicInteger counterTravelBy = new AtomicInteger(0);								//counts the number of habitants that has sent the travelBy action
	private Double entireMoney = 0.0;															//entire population money
	//private Double entireMoneyLeft = 0.0;														//entire population money left
	
	private HashMap<String,PrintWriter> filesOut = new HashMap<String,PrintWriter>();			//map of files and its corresponding print writer
	
    //private List<Double> avgTimeValues  = new ArrayList<Double>();								//average time values of each iteration
	private HashMap<Integer,List<Double>> avgTimeValues  = new HashMap<Integer,List<Double>>();		//average time values of each iteration
    //private List<Double> avgTimeTrainValues  = new ArrayList<Double>();								//average time values of train for each iteration
    private HashMap<Integer,List<Double>> avgTimeTrainValues  = new HashMap<Integer,List<Double>>();	//average time values of train for each iteration
    private List<Double> avgTimeDelays  = new ArrayList<Double>();								//average time values of each iteration
	//private List<Double> standardDevTimeValuesUp  = new ArrayList<Double>();					//average time values adding its standard deviation of each iteration
	//private List<Double> standardDevTimeValuesDown  = new ArrayList<Double>();					//average time values substracting its standard deviation of each iteration
	//private List<Double> entireMoneyValues  = new ArrayList<Double>();						//summation entire money values
	private List<Double> entireMoneyLeftValues  = new ArrayList<Double>();						//summation entire money left values
	private List<Double> avgCO2Emissions = new ArrayList<Double>();								//average CO2 emissions of each iteration
	//private List<Double> standardDevCO2EmissionsUp = new ArrayList<Double>();					//average CO2 emissions adding its standard deviation of each iteration
	//private List<Double> standardDevCO2EmissionsDown = new ArrayList<Double>();					//average CO2 emissions substracting its standard deviation of each iteration
	//private List<Double> [] totalHabsPerVehicle = new ArrayList[VehicleType.N_TYPES];			//number of habitants travelling by vehicle type
	//private List<List<Double> > totalHabsPerVehicle = new ArrayList<List<Double> >();			//number of habitants travelling by vehicle type
	private HashMap<Integer,List<List<Double> > > totalHabsPerVehicle = new HashMap<Integer,List<List<Double> > >();			//number of habitants travelling by vehicle type
	private List<Double> entireNumVehicles = new ArrayList<Double>();							//entire vehicles in use
	private List<Double> avgNumHabsXVehicle = new ArrayList<Double>();							//average number of passengers in the vehicles
	private HashMap<Integer,List<Double>> estimatedTimeHabitants = new HashMap<Integer,List<Double>>();	//estimated time values for each habitant

	private List<SimulationElement> simElementList = new ArrayList<SimulationElement>();				//values for and from the simulator
	private List<SimulationElement> simElementListNotRoad = new ArrayList<SimulationElement>();			//values of vehicles that not traves on road
	private HashMap<Vehicle,List<Integer>> whoTravelBy = new HashMap<Vehicle,List<Integer>>();			//habitants who travel in vehicle at road
	private HashMap<Vehicle,List<Integer>> whoTravelByNotRoad = new HashMap<Vehicle,List<Integer>>();	//habitants who travel in vehicle not at road
	private HashMap<String,List<Literal>> infoForHabs = new HashMap<String,List<Literal>>();			//messages to send for each habitant
	
	private HashMap<Integer,StatCalc> statTimeVillage = new HashMap<Integer,StatCalc>(); 
	private HashMap<Integer,StatCalc> statTimeTrainVillage = new HashMap<Integer,StatCalc>(); 
	private StatCalc statTime, statTimeTrain, statDelay;			//statics for time values
	private StatCalc statConsumption;					//statics for consumption values
	private StatCalc statPassengers;					//statics for passengers in a vehicle values
	
	private enum States {LAUNCH, INIT, START_DECISION, SIMULATE, NOTIFY, LOG};	//states that the world could be
	private States state = States.LAUNCH; 										//world state

	/**
	 * initialize the environment
	 *
	 * @param args	info from the mas2j (sleep time, wheter display the gui, number of iterations, number of agents, size of a group agents)
	 */
    @Override
	public void init(String[] args) {	
		super.init(new String[] { "1000000" } ); // set step timeout (we give a high value because we are not interested in restricting the action time of the agents)
		numHabitants = (Integer.parseInt(args[3]));
		super.setNbAgs(numHabitants);
		habitantsXGroup = (Integer.parseInt(args[4]));
		setOverActionsPolicy(OverActionsPolicy.ignoreSecond);
		if(hasLog)
			createLogFiles(numHabitants);
		nIter = (Integer.parseInt(args[2]));
        hasGUI = args[1].equals("yes"); 
        sleep  = Integer.parseInt(args[0]);
		initWorld();
		logger.info("End of initWorld()");
		updateAgsPercept();
    }

	/**
	 * create the logs files of simulation results. One for each agent, one for global results and one for global emissions results
	 *
	 * @param n	the number of habitants in the world
	 */
	private void createLogFiles(int n){
		File f,dir;
		Calendar cal = Calendar.getInstance();
		String directory;
		
		try{
			//create the directory
			dir = new File("logs/");
			if (!dir.exists()){dir.mkdir();}
			directory = cal.get(Calendar.YEAR)+""
				+(cal.get(Calendar.MONTH)+1)/10+""+(cal.get(Calendar.MONTH)+1)%10+""
				+cal.get(Calendar.DAY_OF_MONTH)/10+""+cal.get(Calendar.DAY_OF_MONTH)%10+"  "
				+(cal.get(Calendar.HOUR)+cal.get(Calendar.AM_PM)*12)/10+""+(cal.get(Calendar.HOUR)+cal.get(Calendar.AM_PM)*12)%10+"."
				+cal.get(Calendar.MINUTE)/10+""+cal.get(Calendar.MINUTE)%10+"."
				+cal.get(Calendar.SECOND)/10+""+cal.get(Calendar.SECOND)%10;
			dir = new File("logs/"+directory);
			dir.mkdir();
			
			//create the files
			for (int i=0; i<n; i++){
				f = new File("logs/"+directory+"/habitant"+(i+1)+".txt");
				filesOut.put("habitant"+(i+1),new PrintWriter(new BufferedWriter(new FileWriter(f))));
				f.createNewFile();
			}
			f = new File("logs/"+directory+"/global.txt");
			filesOut.put("global",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/CO2Emissions.txt");
			filesOut.put("CO2Emissions",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/time.txt");
			filesOut.put("time",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/time_train.txt");
			filesOut.put("time_train",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/co2.txt");
			filesOut.put("co2",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/ppcar.txt");
			filesOut.put("ppcar",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/hab_car.txt");
			filesOut.put("hab_car",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			f = new File("logs/"+directory+"/hab_train.txt");
			filesOut.put("hab_train",new PrintWriter(new BufferedWriter(new FileWriter(f))));
			f.createNewFile();
			
			//introduce the head of the files
			PrintWriter pw;
			for (int j=0; j<numHabitants; j++){
				pw = filesOut.get("habitant"+(j+1));
				pw.println("iter"+"\t"+"vehicle"+"\t"+"calTime"+"\t"+"errTime"+"\t"+"calCons"+"\t"+"errCons");
			}
			pw = filesOut.get("global");
			pw.println("iter"+"\t"+"sumTime"+"\t"+"avgTime"+"\t"+"devTime"+"\t"+"minTime"+"\t"+"maxTime"+"\t"+"sumCons"+"\t"+"avgCons"+"\t"+"DevCons"+"\t"+"minCons"+"\t"+"maxCons");
			pw = filesOut.get("CO2Emissions");
			pw.println("iter"+"\t"+"Kg CO2"+"\t"+"avg CO2"+"\t"+"devCO2");
			
		} catch(IOException e){
			logger.warning("Error "+e);
		}
	}
    
	/**
	 * actions to execute at the beginning of an iteration before each agents step
	 *
	 * @param step	number of actual step
	 */
	@Override
	synchronized protected void stepStarted(int step) {
		if(!running){
			logger.info("SIMULATION FINISHED");
		}else{
			logger.info("Step " + step + " started");
		}
		
		//to begin the states machine process
		if (state == States.LAUNCH)
		{
				logger.info("Start of state: LAUNCH");
				logger.info("Start of state: INIT");
				state = States.INIT;
		}
		
    }
	
	/**
	 * when all the habitants has sent its action to the environment this method executes them
	 *
	 * @param ag	the habitant that executes the action
	 * @param action	the action that habitant requests
	 *
	 * @return true if no exception happens
	 */
    @Override
    public boolean executeAction(String ag, Structure action) {
		Term [] termArray = action.getTermsArray();
        try {
            if (sleep > 0) {
                Thread.sleep(sleep);
            }
			
			//if the action of the habitant is like travelBy(vehicle,time of arriving estimated,time to exit,estimated consumption).
			if (action.getFunctor().equals("travelBy")){
				String vehicle = termArray[0].toString();
				Float atWork = Float.parseFloat(termArray[1].toString());
				Float exitTime = Float.parseFloat(termArray[2].toString());
				Float sCons = Float.parseFloat(termArray[3].toString());
				int agId = getAgIdBasedOnName(ag);
				Double estimatedTime = atWork.doubleValue() - exitTime.doubleValue();
				
				//add estimated time values for each habitant
				if (!estimatedTimeHabitants.containsKey(agId)){
					List<Double> estimatedTimeValues = new ArrayList<Double>();
					estimatedTimeValues.add(estimatedTime);
					estimatedTimeHabitants.put(agId,estimatedTimeValues);
				}else {
					List<Double> estimatedTimeValues = estimatedTimeHabitants.get(agId);
					estimatedTimeValues.add(estimatedTime);
					estimatedTimeHabitants.put(agId,estimatedTimeValues);
				}
				
				//get the vehicle
				Vehicle myVehicle = model.getVehicle(vehicle);
				int villageId = model.getHabitant(agId).getVillageId();
				
				//increments the number of vehicles of a type
				//logger.info("ordinal: " + myVehicle.getVehicleType().ordinal() + " of "+ VehicleType.N_TYPES );
				habsPerVehicle.get(villageId)[myVehicle.getVehicleType().ordinal() ] += 1;
				
				// get the entry for the road for the village of the habitant
				Village vil = model.getVillage(villageId);
				Float enterKm = vil.getEnterRoadKm();
				
				//create the map vehicle-occupants and the list for the simulator
				if ((myVehicle.getVehicleType().equals(VehicleType.bus)) || (myVehicle.getVehicleType().equals(VehicleType.car)) || (myVehicle.getVehicleType().equals(VehicleType.moto))) 
				{	
					if (!whoTravelBy.containsKey(myVehicle)){
						counterVehicles++;
						List<Integer> agIds = new ArrayList<Integer>();
						agIds.add(agId);
						whoTravelBy.put(myVehicle,agIds);
						SimulationElement simElem = new SimulationElement(myVehicle,exitTime,enterKm,atWork,sCons);
						simElementList.add(simElem);
					} else{
						List<Integer> agIds = whoTravelBy.get(myVehicle);
						agIds.add(agId);
						whoTravelBy.put(myVehicle,agIds);
					}
				} else{ // Train, Bike
					if (!whoTravelByNotRoad.containsKey(myVehicle)){
						List<Integer> agIds = new ArrayList<Integer>();
						agIds.add(agId);
						whoTravelByNotRoad.put(myVehicle,agIds);
						SimulationElement simElem = new SimulationElement(myVehicle,exitTime,enterKm,atWork,sCons);
						// calculate arrive time, no need of simulator
						if (myVehicle.getVehicleType().equals(VehicleType.train) )
						{
							// Going by train there are some delays (5 min)
							float arriveTime = exitTime + 5*60 + model.getRoadLength() / (myVehicle.getVehicleMaxVel() / 3600 );
							simElem.setConsumption(0f);
							simElem.setCalculatedTime(arriveTime);
						} else {
							float arriveTime = exitTime + model.getRoadLength() / (myVehicle.getVehicleMaxVel() / 3600 );
							simElem.setConsumption(0f);
							simElem.setCalculatedTime(arriveTime);
						}
						simElementListNotRoad.add(simElem);
					} else{
						List<Integer> agIds = whoTravelByNotRoad.get(myVehicle);
						agIds.add(agId);
						whoTravelByNotRoad.put(myVehicle,agIds);
					}
				}
				
				//If the habitant to execute the action is the last habitant and we have all the information
				if (counterTravelBy.incrementAndGet()==numHabitants){
					logger.info("End of state: START_DECISION");
					logger.info("Start of state: SIMULATE");
					state = States.SIMULATE;
					
					//launch the simulation and pick up the results
					Village vill = model.getVillage(100);
					if(!simElementList.isEmpty()){
						
						// Add the penalty time and consumption of collecting travelling companions 
						Vehicle ve; 
						Float penaltyPartner;
						for (SimulationElement simElem : simElementList){
							ve = simElem.getVehicle();
							if ((ve.getVehicleType().equals(VehicleType.car)) || (ve.getVehicleType().equals(VehicleType.moto))){
								int passen = whoTravelBy.get(ve).size();
								penaltyPartner = (passen-1) * 5*60f;  //*(float)(Math.random());
								//penaltyPartner = 0f;
								simElem.setEnterRoadTime(simElem.getExitTime()+penaltyPartner);
								// Estimated vel. while collecting partners: 40 Km/h
								simElem.setConsumption(simElem.getVehicle().getConsumption() * penaltyPartner/3600 *40 /100);
							}
						}
						
						simElementList = vill.getRoad().simulate(simElementList);
					}
					
					logger.info("End of state: SIMULATE");
					logger.info("Start of state: LOG");
					state = States.LOG;
					
					//add to the lists the number of vehicle types used
					for(Village v : model.getVillageList())
					{
						for(VehicleType type: VehicleType.values() ) {
							//totalHabsPerVehicle[type.ordinal() ].add( habsPerVehicle[type.ordinal() ] );
							totalHabsPerVehicle.get(v.getVillageId()).get(type.ordinal() ).add( habsPerVehicle.get(v.getVillageId())[type.ordinal() ] );
						}
					}
					entireNumVehicles.add(counterVehicles);
					
					//write the data log
					toLog();
					logger.info("End of state: LOG");
					logger.info("Start of state: NOTIFY");
					state = States.NOTIFY;
					
					//send the simulation results to the habitants
					sendResults();	
					counterTravelBy.set(0);
					
				}
			} else if (action.getFunctor().equals("end")){
				//whether the habitant to execute the action is the last habitant stop the process
				if (counterEnd.incrementAndGet()==numHabitants){
					//getEnvironmentInfraTier().getRuntimeServices().stopMAS(); //whether we want stop the process when all iterations have been simulated we will use this instruction
					running=false;
				}
			}
        } catch (Exception e) {
            logger.log(Level.SEVERE, "error executing " + action + " for " + ag, e);
        }
		return true;
    }
	
	/**
	 * actions to execute before each agents step
	 *
	 * @param step	number of actual step
	 * @param time	duration of the step
	 * @param timeout	if timeout has been exceeded
	 */
	@Override
    synchronized protected void stepFinished(int step, long time, boolean timeout) {
    	logger.info("step "+step+" finished in "+time+" ms.");
		
		//actions according the current world state
		switch (state)
		{	
			case INIT:
				logger.info("End of state: INIT");
				if (actualIter == 1){
					logger.info("____________________--- BEGIN ITERATION "+actualIter+" ---____________________");
				}
				logger.info("Start of state: START_DECISION");
				state = States.START_DECISION;
				
				for (int i=1; i<=numHabitants; i++){
					List<Literal> infoList = new ArrayList<Literal>();
					infoList.add(ASSyntax.createLiteral("iteration", ASSyntax.createNumber(actualIter)));
					infoList.add(ASSyntax.createLiteral("start"));
					List<Literal> previousInfo = new ArrayList<Literal>();
					if (infoForHabs.containsKey("habitant"+i)){
						previousInfo = infoForHabs.get("habitant"+i);
					}
					previousInfo.addAll(infoList);
					infoForHabs.put("habitant"+i,previousInfo);
				}
			break;
			case START_DECISION:
			break;
			case NOTIFY:
				logger.info("End of state: NOTIFY");
				if (hasGUI){ updateGraph(); }
				newIteration();
				logger.info("Start of state: START_DECISION");
				state = States.START_DECISION;
			break;
			default:
				logger.severe("Unknown state to end");
			break;			
		}
    }
	
	/**
	 * update the graphs of the model view adding the calculated values
	 */
	private void updateGraph(){
		for (Village v : model.getVillageList())
		{
			view.addSerieTime("Car-Village " + v.getVillageId(), getData(avgTimeValues.get(v.getVillageId())));
			view.addSerieTime("Train-Village " + v.getVillageId(), getData(avgTimeTrainValues.get(v.getVillageId())));		
			//view.addSerieTime("average delay times", getData(avgTimeDelays));
			//view.addSerieTime("time avg + ?", getData(standardDevTimeValuesUp));
			//view.addSerieTime("time avg - ?", getData(standardDevTimeValuesDown));

			view.addSerieMean("Car-Village " + v.getVillageId(), getData(totalHabsPerVehicle.get(v.getVillageId()).get(VehicleType.car.ordinal() ) ));
			view.addSerieMean("Train-Village " + v.getVillageId(), getData(totalHabsPerVehicle.get(v.getVillageId()).get(VehicleType.train.ordinal() ) ));
			//view.addSerieMean("vehicles in use", getData(entireNumVehicles));
		}
		view.addSerieMoney("", getData(entireMoneyLeftValues));
		//view.addSerieMoney("summation maximum money", getData(entireMoneyValues));
		view.addSerieEmissions("", getData(avgCO2Emissions));
		//view.addSerieEmissions("(+)standard deviation", getData(standardDevCO2EmissionsUp));
		//view.addSerieEmissions("(-)standard deviation", getData(standardDevCO2EmissionsDown));
		//for (int i=0; i<numHabitants; i++){
		//	view.addSerieEstimatedTime("habitant"+(i+1), getData(estimatedTimeHabitants.get(i)));
		//}
		view.addSeriePassengers("", getData(avgNumHabsXVehicle));
	}
	
	/**
	 * get the values to set at graphs
	 */
	private double[][] getData(List<Double> values) {
        double[][] r = new double[2][values.size()];
        int i = 0;
        for (double v: values) {
            r[0][i] = i;
            r[1][i] = v;
            i++;
        }
        return r;
    }
	
	/**
	 * calculate the statistics with the values from the simulation elements and write them to the logs
	 */
	private void toLog(){
		DecimalFormat form = new DecimalFormat("########.##");
		PrintWriter	pw;
		Vehicle v;
		float calcTime, stmTime, stmCons, calcCons;
		double moneySpent;
		for (Village vil : model.getVillageList())
		{
		  statTimeVillage.put(vil.getVillageId(), new StatCalc());
		  statTimeTrainVillage.put(vil.getVillageId(), new StatCalc());
		}
		statTime = new StatCalc();
		statTimeTrain = new StatCalc();
		statDelay = new StatCalc();
		statConsumption = new StatCalc();
		statPassengers = new StatCalc();
		//for each simulation elements calculate the statistics and write the particular results on the habitants logs
		for (SimulationElement simElem : simElementList){
			v = simElem.getVehicle();
			if ((v.getVehicleType().equals(VehicleType.car)) || (v.getVehicleType().equals(VehicleType.moto))){
				statPassengers.enter(whoTravelBy.get(v).size());
			}
			calcTime = simElem.getCalculatedTime()-simElem.getExitTime();
			calcCons = simElem.getConsumption();
			stmTime = simElem.getAtWork()-simElem.getExitTime();
			stmCons = simElem.getEstimatedConsumption();
			for (int agId : whoTravelBy.get(v)){
				if(hasLog) {
				pw = filesOut.get("habitant"+(agId+1));
				pw.println(actualIter+"\t"+v.getVehicleId()+"   \t"
					+form.format(calcTime)+"\t"+form.format((calcTime-stmTime))+"\t"+form.format(calcCons)+"\t"+form.format((calcCons-stmCons)));
				}
				statTimeVillage.get(model.getHabitant(agId).getVillageId()).enter(calcTime);
				statTime.enter(calcTime);
				statDelay.enter(simElem.getCalculatedTime() - simElem.getAtWork());
				statConsumption.enter(calcCons/(whoTravelBy.get(v).size()));
			}
		}
		for (SimulationElement simElem : simElementListNotRoad){
			v = simElem.getVehicle();
			calcTime = simElem.getCalculatedTime()-simElem.getExitTime();
			calcCons = simElem.getConsumption();
			stmTime = simElem.getAtWork()-simElem.getExitTime();
			stmCons = simElem.getEstimatedConsumption();
			for (int agId : whoTravelByNotRoad.get(v)){
				if(hasLog) {
				pw = filesOut.get("habitant"+(agId+1));
				pw.println(actualIter+"\t"+v.getVehicleId()+"   \t"
					+form.format(calcTime)+"\t"+form.format((calcTime-stmTime))+"\t"+form.format(calcCons)+"\t"+form.format((calcCons-stmCons)));
				}
				statTimeTrainVillage.get(model.getHabitant(agId).getVillageId()).enter(calcTime);
				statTimeTrain.enter(calcTime);
				//statDelay.enter(simElem.getCalculatedTime() - simElem.getAtWork());
			}
			statConsumption.enter(calcCons);
		}
		
		//write the global statistics for to the global log
		if(hasLog) {
		pw = filesOut.get("global");
		pw.println(actualIter+"\t"+form.format(statTime.getSum())+"\t"+form.format(statTime.getMean())+"\t"
				   +form.format(statTime.getStandardDeviation())+"\t"+form.format(statTime.getMin())+"\t"+form.format(statTime.getMax())+"\t"
				   +form.format(statConsumption.getSum())+"\t"+form.format(statConsumption.getMean()) +"\t"
				   +form.format(statConsumption.getStandardDeviation())+"\t"+form.format(statConsumption.getMin())+"\t"
				   +form.format(statConsumption.getMax()));
		   
		//write the emissions statistics to the emissions log
		pw = filesOut.get("CO2Emissions");
		pw.println(actualIter+"\t"+form.format(statConsumption.getSum()*model.getKgCO2XLitreGas())+"\t"
				   +form.format(statConsumption.getMean()*model.getKgCO2XLitreGas())+"\t"
				   +form.format(statConsumption.getStandardDeviation()*model.getKgCO2XLitreGas()));
		}
		
		//add data to the graphs values
		for (Village vil : model.getVillageList())
		{
			avgTimeValues.get(vil.getVillageId()).add(statTimeVillage.get(vil.getVillageId()).getMean());
			avgTimeTrainValues.get(vil.getVillageId()).add(statTimeTrainVillage.get(vil.getVillageId()).getMean());
		}
		avgTimeDelays.add(statDelay.getMean());
		avgNumHabsXVehicle.add(statPassengers.getMean());
		
		if(hasLog) {
		pw = filesOut.get("time");
		pw.println(actualIter+"\t"+ form.format(statTime.getMean() ) );
		
		pw = filesOut.get("time_train");
		pw.println(actualIter+"\t"+ form.format(statTimeTrain.getMean() ) );
		
		pw = filesOut.get("ppcar");
		pw.println(actualIter+"\t"+ form.format(statPassengers.getMean() ) );
		}

		//standardDevTimeValuesUp.add(statTime.getMean()+statTime.getStandardDeviation());
		//standardDevTimeValuesDown.add(statTime.getMean()-statTime.getStandardDeviation());
		//entireMoneyLeft -= statConsumption.getSum()*model.getPriceXLitreGas();
		moneySpent = statConsumption.getSum()*model.getPriceXLitreGas();
		entireMoneyLeftValues.add(moneySpent);
		//entireMoneyValues.add(entireMoney);
		avgCO2Emissions.add(statConsumption.getMean()*model.getKgCO2XLitreGas());
		
		if(hasLog) {
		pw = filesOut.get("co2");
		pw.println(actualIter+"\t"+ form.format(statConsumption.getMean()*model.getKgCO2XLitreGas() ) );

		// Habs per vehicle type. Careful !! calculated in executeAction()
		pw = filesOut.get("hab_car");
		//pw.println(actualIter+"\t"+ form.format(numHabsTravelByCar.get(numHabsTravelByCar.size()-1 ) ) );
		pw = filesOut.get("hab_train");
		//pw.println(actualIter+"\t"+ form.format(numHabsTravelByTrain.get(numHabsTravelByTrain.size()-1 ) ) );
		}
	}
	
	/**
	 * get values and add the results to list information that will be send to the habitants
	 */
	private void sendResults(){
		Vehicle v;
		int partners;
		float calcTime, calcCons;
		double maxConsumption=statConsumption.getMax();
		double maxTime = statTime.getMax();
		//for each simulation element calculate the values to send to the habitants 
		for (SimulationElement simElem : simElementList){
			v = simElem.getVehicle();
			calcTime = simElem.getCalculatedTime()-simElem.getExitTime();
			calcCons = simElem.getConsumption();
			partners = whoTravelBy.get(v).size();
			for (int agId : whoTravelBy.get(v)){
				//for each agent who travels by a vehicle calculate and store the values to send him
				List<Literal> infoList = new ArrayList<Literal>();
				infoList.add(ASSyntax.createLiteral("realValues", ASSyntax.createAtom(v.getVehicleId()),
					                                              ASSyntax.createAtom(v.getVehicleType().toString()),
					                                              ASSyntax.createNumber(calcTime),
					                                              ASSyntax.createNumber(calcCons)) );
				infoList.add(ASSyntax.createLiteral("maxTime", ASSyntax.createNumber(maxTime)));
				infoList.add(ASSyntax.createLiteral("maxConsumption", ASSyntax.createNumber(maxConsumption)));
				List<Literal> previousInfo = new ArrayList<Literal>();
				if (infoForHabs.containsKey("habitant"+(agId+1))){
					previousInfo = infoForHabs.get("habitant"+(agId+1));
				}
				previousInfo.addAll(infoList);
				infoForHabs.put("habitant"+(agId+1),previousInfo);
			}
		}
		for (SimulationElement simElem : simElementListNotRoad){
			v = simElem.getVehicle();
			calcTime = simElem.getCalculatedTime()-simElem.getExitTime();
			calcCons = simElem.getConsumption();
			partners = whoTravelByNotRoad.get(v).size();
			for (int agId : whoTravelByNotRoad.get(v)){
				//for each agent who travels by a vehicle calculate and store the values to send him
				List<Literal> infoList = new ArrayList<Literal>();
				infoList.add(ASSyntax.createLiteral("realValues", ASSyntax.createAtom(v.getVehicleId()),
					                                              ASSyntax.createAtom(v.getVehicleType().toString()),
					                                              ASSyntax.createNumber(calcTime),
					                                              ASSyntax.createNumber(calcCons/partners)) );
				infoList.add(ASSyntax.createLiteral("maxTime", ASSyntax.createNumber(maxTime)));
				infoList.add(ASSyntax.createLiteral("maxConsumption", ASSyntax.createNumber(maxConsumption)));
				List<Literal> previousInfo = new ArrayList<Literal>();
				if (infoForHabs.containsKey("habitant"+(agId+1))){
					previousInfo = infoForHabs.get("habitant"+(agId+1));
				}
				previousInfo.addAll(infoList);
				infoForHabs.put("habitant"+(agId+1),previousInfo);
			}
		}
	}
	
	/**
	 * if the last iteration not arrives begins a new iteration, else store the end message for the habitants
	 */
	private void newIteration(){
		List<Literal> infoList;
		//clear the values of actual iteration
		
		for(Village v : model.getVillageList())
			for(VehicleType type: VehicleType.values() ) {
				habsPerVehicle.get(v.getVillageId())[type.ordinal() ] = 0.0;
			}
		counterVehicles = 0.0;
		simElementList.clear();
		whoTravelBy.clear();
		simElementListNotRoad.clear();
		whoTravelByNotRoad.clear();
		actualIter++;
		if (actualIter <= nIter){
			logger.info("____________________--- BEGIN ITERATION "+actualIter+" ---____________________");
		}
		//if the collect day arrives store the new capital to the habitats
		if ((actualIter-1)%model.getCollectDay() == 0){
			logger.info("____________________--- COLLECT DAY ARRIVES! ---____________________");
			for(Village v : model.getVillageList()){
				//double auxMoney = 0;
				for (Habitant h1 : v.getHabitantList())
				{
					//entireMoneyLeft += h1.getHabitantSalary();
					//int newCapital = h1.getHabitantCapital() + h1.getHabitantSalary();

					//auxMoney += h1.getHabitantSalary();
					int newCapital = h1.getHabitantSalary();
					//h1.setHabitantCapital(newCapital);
					
					infoList = new ArrayList<Literal>();
					infoList.add(ASSyntax.createLiteral("capital", ASSyntax.createNumber(newCapital)));

					List<Literal> previousInfo = new ArrayList<Literal>();
					if (infoForHabs.containsKey("habitant"+(h1.getHabitantId()+1))){
						previousInfo = infoForHabs.get("habitant"+(h1.getHabitantId()+1));
					}
					previousInfo.addAll(infoList);
					infoForHabs.put("habitant"+(h1.getHabitantId()+1),previousInfo);					
				}
				//entireMoneyLeft = auxMoney;
			}
		}
		//if the last iteration not arrives store the information for the next iteration
		if (actualIter <= nIter){
			for (int i=1; i<=numHabitants; i++){
				infoList = new ArrayList<Literal>();
				infoList.add(ASSyntax.createLiteral("iteration", ASSyntax.createNumber(actualIter)));
				infoList.add(ASSyntax.createLiteral("start"));
				
				List<Literal> previousInfo = new ArrayList<Literal>();
				if (infoForHabs.containsKey("habitant"+i)){
					previousInfo = infoForHabs.get("habitant"+i);
				}
				previousInfo.addAll(infoList);
				infoForHabs.put("habitant"+i,previousInfo);
			}
		}
		//if the last iteration arrives store the end message for the habitants and close log files
		else{
			for (int i=1; i<=numHabitants; i++){
				infoList = new ArrayList<Literal>();
				infoList.add(ASSyntax.createLiteral("end"));
				
				List<Literal> previousInfo = new ArrayList<Literal>();
				if (infoForHabs.containsKey("habitant"+i)){
					previousInfo = infoForHabs.get("habitant"+i);
				}
				previousInfo.addAll(infoList);
				infoForHabs.put("habitant"+i,previousInfo);
				
				if(hasLog) {
					(filesOut.get("habitant"+i)).close();
				}
			}
			if(hasLog) {			
				(filesOut.get("global")).close();
				(filesOut.get("CO2Emissions")).close();
				(filesOut.get("time")).close();
				(filesOut.get("time_train")).close();
				(filesOut.get("co2")).close();
				(filesOut.get("ppcar")).close();
				(filesOut.get("hab_car")).close();
				(filesOut.get("hab_train")).close();
			}
		}
	}
	
	/**
	 * get the agent identifier
	 *
	 * @param agName	name of the agent 
	 *
	 * @return identifier of the agent
	 */
    private int getAgIdBasedOnName(String agName) {
        return (Integer.parseInt(agName.substring(8))) - 1;
    }
    
	/**
	 * creates the world model, init the habitants and begin the simulation
	 */
    private void initWorld() {
    	try {
	        model = new WorldModel(numHabitants, habitantsXGroup);
			//model.mostrarModelo(); //to show the model uncomment that line
			initHabitants();
			
			for(Village v : model.getVillageList()){
				for (Habitant h : v.getHabitantList()){
					entireMoney+=h.getHabitantCapital();
				}
				avgTimeValues.put(v.getVillageId(), new ArrayList<Double>());
				avgTimeTrainValues.put(v.getVillageId(), new ArrayList<Double>());
				
				totalHabsPerVehicle.put(v.getVillageId(), new ArrayList<List<Double> >());
				habsPerVehicle.put(v.getVillageId(), new Double[VehicleType.N_TYPES]);
				for(VehicleType type: VehicleType.values() ) {
					habsPerVehicle.get(v.getVillageId())[type.ordinal() ] = 0.0;
					//totalHabsPerVehicle[type.ordinal() ] = new ArrayList<Double>();
					totalHabsPerVehicle.get(v.getVillageId()).add( new ArrayList<Double>() );
				}

			}
			//entireMoneyLeft = entireMoney;
			
            if (hasGUI) {
                view = new WorldView(model);
                view.setEnv(this);
				beginSimulation();
            } else{
				beginSimulation();
			}
    	} catch (Exception e) {
    		logger.warning("Error creating world "+e);
    	}
    }
	
	/**
	 * init the habitants of the world
	 */
	private void initHabitants(){
		List<Literal> infoList;
		Location city = model.getCityPos();
		int timeInSeconds;
		//at this moment the model has got only one village, but it is ready to work with more than one
		for(Village v : model.getVillageList()){
			for (Habitant h1 : v.getHabitantList()){
				infoList = new ArrayList<Literal>();
				timeInSeconds = Integer.parseInt(h1.getHabitantStartAt().substring(0,2))*3600 + Integer.parseInt(h1.getHabitantStartAt().substring(3,5));

				infoList.add(ASSyntax.createLiteral("iteration", ASSyntax.createNumber(actualIter)));

				infoList.add(ASSyntax.createLiteral("atWork", ASSyntax.createNumber(timeInSeconds)));
				infoList.add(ASSyntax.createLiteral("city", ASSyntax.createNumber(city.x), ASSyntax.createNumber(city.y)));
				infoList.add(ASSyntax.createLiteral("village", ASSyntax.createNumber(v.getVillagePosX()), ASSyntax.createNumber(v.getVillagePosY())));
				infoList.add(ASSyntax.createLiteral("myPosition", ASSyntax.createNumber(h1.getHabitantPosX()), ASSyntax.createNumber(h1.getHabitantPosY())));
				infoList.add(ASSyntax.createLiteral("chargeDay", ASSyntax.createNumber(model.getCollectDay())));
				infoList.add(ASSyntax.createLiteral("toll", ASSyntax.createNumber(model.getToll())));
				infoList.add(ASSyntax.createLiteral("lowerLevelLearningFactor", ASSyntax.createNumber(model.getLowerLevelLearningFactor())));
				infoList.add(ASSyntax.createLiteral("higherLevelLearningFactor", ASSyntax.createNumber(model.getHigherLevelLearningFactor())));
				infoList.add(ASSyntax.createLiteral("lowerLevelForgettingFactor", ASSyntax.createNumber(model.getLowerLevelForgettingFactor())));
				infoList.add(ASSyntax.createLiteral("higherLevelForgettingFactor", ASSyntax.createNumber(model.getHigherLevelForgettingFactor())));
				infoList.add(ASSyntax.createLiteral("priceXLitreGas", ASSyntax.createNumber(model.getPriceXLitreGas())));
				infoList.add(ASSyntax.createLiteral("kgCo2XLitreGas", ASSyntax.createNumber(model.getKgCO2XLitreGas())));
				infoList.add(ASSyntax.createLiteral("capital", ASSyntax.createNumber(h1.getHabitantCapital())));
				infoList.add(ASSyntax.createLiteral("salary", ASSyntax.createNumber(h1.getHabitantSalary())));
				infoList.add(ASSyntax.createLiteral("roadLength", ASSyntax.createNumber(model.getRoadLength())));
				infoList.add(ASSyntax.createLiteral("roadSpeed", ASSyntax.createNumber(model.getRoadSpeed())));
				infoList.add(ASSyntax.createLiteral("roadFlow", ASSyntax.createNumber(model.getRoadFlow())));
				
				//get all information on vehicles available to the habitant
				for (Vehicle ve: v.getCityVehicleList()){
					infoList.add(ASSyntax.createLiteral("cityVehicle", ASSyntax.createAtom(ve.getVehicleId()),
						                                               ASSyntax.createAtom(ve.getVehicleType().toString()),
						                                               ASSyntax.createNumber(ve.getVehicleSeats()),
						                                               ASSyntax.createNumber(ve.getVehicleMaxVel()),
						                                               ASSyntax.createNumber(ve.getConsumption())));
				}
				for (Vehicle ve: h1.getVehicleList()){
					infoList.add(ASSyntax.createLiteral("myVehicle", ASSyntax.createAtom(ve.getVehicleId()),
						                                             ASSyntax.createAtom(ve.getVehicleType().toString()),
						                                             ASSyntax.createNumber(ve.getVehicleSeats()),
						                                             ASSyntax.createNumber(ve.getVehicleMaxVel()),
						                                             ASSyntax.createNumber(ve.getConsumption())));
				}
				
				for (Group g : v.getVillageGroups()){
					if (g.getHabitants().contains(h1)){
						for (Habitant h2 : g.getHabitants()){
							if (h1 != h2){
								infoList.add(ASSyntax.createLiteral("neighbor", ASSyntax.createAtom("habitant"+(h2.getHabitantId()+1))));
								for (Vehicle vOth : h2.getVehicleList()){
									if (!(vOth.getVehicleType().equals(VehicleType.train) || vOth.getVehicleType().equals(VehicleType.bus))){
										infoList.add(ASSyntax.createLiteral("otherVehicle", ASSyntax.createAtom(h2.getHabitantName()),
											                                                ASSyntax.createAtom(vOth.getVehicleId()),
											                                                ASSyntax.createAtom(vOth.getVehicleType().toString()),
											                                                ASSyntax.createNumber(vOth.getVehicleSeats()),
											                                                ASSyntax.createNumber(vOth.getVehicleMaxVel()),
											                                                ASSyntax.createNumber(vOth.getConsumption())));
									}
								}
							}
						}
					}
				}
				List<Literal> previousInfo = new ArrayList<Literal>();
				if (infoForHabs.containsKey("habitant"+(h1.getHabitantId()+1))){
					previousInfo = infoForHabs.get("habitant"+(h1.getHabitantId()+1));
				}
				previousInfo.addAll(infoList);
				infoForHabs.put("habitant"+(h1.getHabitantId()+1),previousInfo);
			}	
		}
	}
    
	/**
	 * init the simulation
	 */
	private void beginSimulation(){
		List<Literal> infoList; 
		logger.info("____________________--- AGENTS INITIALIZATION ---____________________");
		for (int i=1; i<=numHabitants; i++){
			infoList = new ArrayList<Literal>();
			//assigns the group leader to the first habitant of the group
			if ((i%habitantsXGroup == 1)||(habitantsXGroup == 1)){
				infoList.add(ASSyntax.createLiteral("groupLeader", ASSyntax.createAtom("habitant"+i)));
			}
			infoList.add(ASSyntax.createLiteral("initAg"));
			
			List<Literal> previousInfo = new ArrayList<Literal>();
			if (infoForHabs.containsKey("habitant"+i)){
				previousInfo = infoForHabs.get("habitant"+i);
			}
			previousInfo.addAll(infoList);
			infoForHabs.put("habitant"+i,previousInfo);
		}
	}

	/**
	 * update all agents percept and clear the message store
	 */
    protected void updateAgsPercept() {
        for (int i = 0; i < getNbAgs(); i++) {
            updateAgPercept(i);
        }
		infoForHabs.clear();
    }

	/**
	 * update an agent percept
	 *
	 * @param ag	the agent will be updated
	 */
    private void updateAgPercept(int ag) {
        updateAgPercept("habitant" + (ag + 1), ag);
    }

	/**
	 * update agent percept with the stored information
	 *
	 * @param agName	agent name
	 * @param ag		agent identifier
	 */
    private void updateAgPercept(String agName, int ag) {
        clearPercepts(agName);
		if(infoForHabs.containsKey(agName)){
			List<Literal> infoList = infoForHabs.get(agName);
			for (Literal l : infoList){
				super.addPercept(agName, l);
			}
		}
    }

}
