/***************************************************************************
 *                           RealTimeEDLUTKernel.cpp                       *
 *                           -------------------                           *
 * copyright            : (C) 2014 by Francisco Naveros                    *
 * email                : jfnaveros@ugr.es                                 *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <time.h>
#include <math.h> // TODO: maybe remove this

#include <iostream>

#include "../include/simulation/ParamReader.h"
#include "../include/simulation/Simulation.h"

#include "../include/simulation/ParameterException.h"

#include "../include/communication/ConnectionException.h"
#include "../include/communication/InputSpikeDriver.h"
#include "../include/communication/OutputSpikeDriver.h"
#include "../include/communication/OutputWeightDriver.h"

#include "../include/spike/EDLUTFileException.h"
#include "../include/spike/EDLUTException.h"
#include "../include/spike/Network.h"

#include "../include/openmp/openmp.h"


//#include "google/profiler.h"
//#include "vld.h"


using namespace std;

/*!
 * 
 * 
 * \note Obligatory parameters:
 * 			-time Simulation_Time(in_seconds) It sets the total simulation time.
 * 			-nf Network_File	It sets the network description file.
 * 			-wf Weights_File	It sets the weights file.
 * 			
 * \note  parameters:
 * 			-info 	It shows the network information.
 * 			-sf File_Name	It saves the final weights in file File_Name.
 * 			-wt Save_Weight_Step	It sets the step time between weights saving.
 * 			-st Step_Time(in_seconds) It sets the step time in simulation.
 * 			-log File_Name It saves the activity register in file File_Name.
 *          -logp File_Name It saves all events register in file File_Name.
 * 			-if Input_File	It adds the Input_File file in the input sources of the simulation.
 * 			-of Output_File	It adds the Output_File file in the output targets of the simulation.
 *          -openmpQ number_of_OpenMP_queues It sets the number of OpenMP queues.
 *          -openmp number_of_OpenMP_threads It sets the number of OpenMP threads.
 * 			-ic IPAddress:Port Server|Client	It adds the connection as a server or a client in the specified direction in the input sources of the simulation.
 * 			-oc IPAddress:Port Server|Client	It adds the connection as a server or a client in the specified direction in the output targets of the simulation.
 * 			-ioc IPAddress:Port Server|Client	It adds the connection as a server or a client in the specified direction in the input sources and in the output targets.
 *			-rt
 *			-rtgap
 *          -rt1
 *          -rt2
 *          -rt3
 *
  */ 


#include <xmmintrin.h>


int main(int ac, char *av[]) {

	clock_t starttotalt,endtotalt;
	starttotalt=clock();

	clock_t startt,endt;
	cout << "Loading tables..." << endl;

	srand ( time(NULL) );

	try {
   		ParamReader Reader(ac, av);

		Simulation Simul(Reader.GetNetworkFile(), Reader.GetWeightsFile(), Reader.GetSimulationTime(), Reader.GetSimulationStepTime(), Reader.GetNumberOfQueues(), Reader.GetNumberOfThreads());
		
		for (unsigned int i=0; i<Reader.GetInputSpikeDrivers().size(); ++i){
			Simul.AddInputSpikeDriver(Reader.GetInputSpikeDrivers()[i]);
		}
			
		for (unsigned int i=0; i<Reader.GetOutputSpikeDrivers().size(); ++i){
			Simul.AddOutputSpikeDriver(Reader.GetOutputSpikeDrivers()[i]);
		}
		
		for (unsigned int i=0; i<Reader.GetMonitorDrivers().size(); ++i){
			Simul.AddMonitorActivityDriver(Reader.GetMonitorDrivers()[i]);
		}
		
		for (unsigned int i=0; i<Reader.GetOutputWeightDrivers().size(); ++i){
			Simul.AddOutputWeightDriver(Reader.GetOutputWeightDrivers()[i]);
		}
		Simul.SetSaveStep(Reader.GetSaveWeightStepTime());

		if(Reader.CheckInfo()){
			//Simul.GetNetwork()->tables_info();
			//neutypes_info();
			Simul.PrintInfo(cout);
		}

		#ifdef _OPENMP 
			if(Reader.GetRealTimeOption()){
				omp_set_nested(true);
				cout<<"REAL TIME SIMULATION\n"<<endl;
				if(Reader.GetSimulationStepTime()>0.0){
					Simul.RealTimeRestrictionObject->SetParameterWatchDog(Reader.GetSimulationStepTime(), Reader.GetRtGap(), Reader.GetRt1(), Reader.GetRt2(), Reader.GetRt3());
				}else{
					cout<<"Simulation step time must be greater than zero\n"<<endl;
					exit(0);
				}
			}
		#else
			cout<<"\nREAL TIME SIMULATION is not available due to the openMP support is disabled\n"<<endl;
			exit(0);
		#endif

		#pragma omp parallel if(Reader.GetRealTimeOption()) num_threads(2) 
		{
			if(omp_get_thread_num()==1){
				Simul.RealTimeRestrictionObject->Watchdog();
			}else{
				cout << "Simulating network..." << endl;
				Simul.InitSimulation();
				startt=clock();
				double time;
				#pragma omp parallel if(NumberOfOpenMPThreads>1) default(shared) private(time)
				{

					//Select the correspondent device.
					if(NumberOfGPUs>0){
						HANDLE_ERROR(cudaSetDevice(GPUsIndex[openMP_index % NumberOfGPUs])); 
					}

					if(omp_get_thread_num()==0 &&Reader.GetRealTimeOption()){
						Simul.RealTimeRestrictionObject->ResetWatchDog();
					}
					for(time=Simul.GetSimulationStep(); time<Reader.GetSimulationTime(); time+=Simul.GetSimulationStep()){
						if(omp_get_thread_num()==0 && Reader.GetRealTimeOption()){
							Simul.RealTimeRestrictionObject->NextStepWatchDog();
						}
						#pragma omp barrier
						Simul.RunSimulationSlot(time);
					}
				}

				if(Reader.GetRealTimeOption()){
					Simul.RealTimeRestrictionObject->StopWatchDog();
				}
				endt=clock();
			}
		}

		

		// Closing simulation connections
		for (unsigned int i=0; i<Reader.GetInputSpikeDrivers().size(); ++i){
			InputSpikeDriver * Input = Reader.GetInputSpikeDrivers()[i];
			delete Input;
		}

		for (unsigned int i=0; i<Reader.GetOutputSpikeDrivers().size(); ++i){
			OutputSpikeDriver * Output = Reader.GetOutputSpikeDrivers()[i];
			delete Output;
		}

		for (unsigned int i=0; i<Reader.GetMonitorDrivers().size(); ++i){
			OutputSpikeDriver * Monitor = Reader.GetMonitorDrivers()[i];
			delete Monitor;
		}

		for (unsigned int i=0; i<Reader.GetOutputWeightDrivers().size(); ++i){
			OutputWeightDriver * Weights = Reader.GetOutputWeightDrivers()[i];
			delete Weights;
		}

		cout << "Oky doky" << endl;  

		cout << "Elapsed time: " << (endt-startt)/(float)CLOCKS_PER_SEC << " sec" << endl;
		for(int i=0; i<Simul.GetNumberOfQueues(); i++){
			cout << "Thread "<<i<<"--> Number of updates: " << Simul.GetSimulationUpdates(i) << endl; /*asdfgf*/
			cout << "Thread "<<i<<"--> Number of InternalSpike: " << Simul.GetTotalSpikeCounter(i) << endl; /*asdfgf*/
			cout << "Thread "<<i<<"--> Number of Propagated Spikes and Events: " << Simul.GetTotalPropagateCounter(i)<<", "<< Simul.GetTotalPropagateEventCounter(i)<< endl; /*asdfgf*/
			cout << "Thread "<<i<<"--> Mean number of spikes in heap: " << Simul.GetHeapAcumSize(i)/(float)Simul.GetSimulationUpdates(i) << endl; /*asdfgf*/
			cout << "Thread "<<i<<"--> Updates per second: " << Simul.GetSimulationUpdates(i)/((endt-startt)/(float)CLOCKS_PER_SEC) << endl; /*asdfgf*/
		}
		endtotalt=clock();
		cout << "Total elapsed time: " << (endtotalt-starttotalt)/(float)CLOCKS_PER_SEC << " sec" << endl;
		cout << "Total InternalSpike: " << Simul.GetTotalSpikeCounter()<<endl; 
		cout << "Total Propagated Spikes and Events: " << Simul.GetTotalPropagateCounter()<<", "<< Simul.GetTotalPropagateEventCounter()<<endl;


	} catch (ParameterException Exc){
		cerr << Exc << endl;
		cerr << av[0] << " -time Simulation_Time -nf Network_File -wf Weights_File";
		cerr << " [-info] [-sf Final_Weights_File] [-wt Save_Weight_Step] [-st Simulation_Step_Time] [-ts Time_Driven_Step]"; 
		cerr << " [-tsGPU Time_Driven_Step_GPU] [-log Activity_Register_File] [-logp Activity_Register_File] [-if Input_File]";
		cerr << " [-ic IPAddress:Port Server|Client] [-of Output_File] [-oc IPAddress:Port Server|Client] [-ioc IPAddress:Port Server|Client]" << endl;	
	} catch (ConnectionException Exc){
		cerr << Exc << endl;
		return 1;
	} catch (EDLUTFileException Exc){
		cerr << Exc << endl;
		return Exc.GetErrorNum();
	} catch (EDLUTException Exc){
		cerr << Exc << endl;
		return Exc.GetErrorNum();
	}


	return 0;
}