/***************************************************************************
 *                           ParamReader.cpp                               *
 *                           -------------------                           *
 * copyright            : (C) 2009 by Jesus Garrido and Richard Carrillo   *
 * email                : jgarrido@atc.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 "../../include/simulation/ParamReader.h"
#include "../../include/simulation/Simulation.h"

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

#include "../../include/communication/TCPIPConnectionType.h"

#include "../../include/communication/FileInputSpikeDriver.h"
#include "../../include/communication/TCPIPInputSpikeDriver.h"

#include "../../include/communication/FileOutputSpikeDriver.h"
#include "../../include/communication/TCPIPOutputSpikeDriver.h"
#include "../../include/communication/TCPIPInputOutputSpikeDriver.h"

#include "../../include/communication/FileOutputWeightDriver.h"

#include "../../include/communication/ConnectionException.h"

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

#include "../../include/openmp/openmp.h"
 
void ParamReader::ParseArguments(int Number, char ** Arguments) throw (ParameterException, ConnectionException) {
	for (int i=1; i<Number; ++i){
		string CurrentArgument = Arguments[i];
		if(CurrentArgument=="-time"){ // Simulation Total Time
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->SimulationTime))
     				throw ParameterException(Arguments[i], "Invalid simulation time");				
			} else {
				throw ParameterException(Arguments[i],"Invalid simulation time");				
			}
		} else if (CurrentArgument=="-nf"){ // Network configuration file
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->NetworkFile = Arguments[++i];
				if (!this->FileExists(this->NetworkFile)){
					throw ParameterException(Arguments[i],"Invalid network configuration file. The file doesn't exist.");
				}
			} else {
				throw ParameterException(Arguments[i],"Invalid network configuration file");				
			}
		} else if (CurrentArgument=="-wf"){ // Weights configuration file
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->WeightsFile = Arguments[++i];
				if (!this->FileExists(this->WeightsFile)){
					throw ParameterException(Arguments[i],"Invalid weights configuration file. The file doesn't exist.");
				}
			} else {
				throw ParameterException(Arguments[i],"Invalid weights configuration file");				
			}
		} else if (CurrentArgument=="-info"){
			this->NetworkInfo = true;
		} else if (CurrentArgument=="-sf"){
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->OutputWeightDrivers.push_back(new FileOutputWeightDriver (Arguments[++i]));
			}
		} else if (CurrentArgument=="-wt"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->WeightTime))
     				throw ParameterException(Arguments[i], "Invalid saving weight step time");
			} else {
				throw ParameterException(Arguments[i],"Invalid saving weight step time");				
			}
		} else if (CurrentArgument=="-st"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->SimulationStepTime))
     				throw ParameterException(Arguments[i], "Invalid simulation step time");
			} else {
				throw ParameterException(Arguments[i],"Invalid simulation step time");				
			}
		}  else if (CurrentArgument=="-if"){
			if (i+1<Number){
				// Check if it is a valid file and exists
				string File=Arguments[++i];
				if (!this->FileExists(File)){
					throw ParameterException(File,"Invalid input file. The file doesn't exist.");
				}
				this->InputDrivers.push_back(new FileInputSpikeDriver (File.c_str()));
			} else {
				throw ParameterException(Arguments[i],"Invalid input file");				
			}
		} else if (CurrentArgument=="-ic"){
			if (i+2<Number){
				string host = Arguments[i+1];
				string address = "";
				unsigned short port = 0;
				
				string type = Arguments[i+2];
				
				TCPIPInputSpikeDriver * Driver;
				
				enum TCPIPConnectionType Type;

				if (type == string("Server")){
					istringstream ss(host);
					if (!(ss >> port)){
	     				throw ParameterException(Arguments[i+1], "Invalid connection port");
					}
					Type = SERVER;

				}else if (type == string("Client")){
					string::size_type pos = host.find(":",0);
					if ( pos != string::npos ){
						address = host.substr(0, pos);
						istringstream ss(host.substr(pos+1));
						if (!(ss >> port))
	     					throw ParameterException(Arguments[i+1], "Invalid connection port");
					} else {
						// Output error
						throw ParameterException(Arguments[i+1],"Invalid output connection. Check the address and the port.");	
					}
					Type = CLIENT;
				} else {
					// Output error
					throw ParameterException(Arguments[i+2],"Invalid output connection type. Only Server and Client are allowed");
				}
				
				Driver = new TCPIPInputSpikeDriver (Type,address,port);

				i += 2;
				this->InputDrivers.push_back(Driver);
				
			} else {
				throw ParameterException(Arguments[i],"Invalid input connection.");
			}
			
		} else if (CurrentArgument=="-log"){
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->MonitorDrivers.push_back(new FileOutputSpikeDriver (Arguments[++i],false));
			}
		} else if (CurrentArgument=="-logp"){
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->MonitorDrivers.push_back(new FileOutputSpikeDriver (Arguments[++i],true));
			}
		} else if (CurrentArgument=="-of"){
			if (i+1<Number){
				// Check if it is a valid file and exists
				this->OutputDrivers.push_back(new FileOutputSpikeDriver (Arguments[++i],false));
			}
		} else if (CurrentArgument=="-oc"){
			if (i+2<Number){
				string host = Arguments[i+1];
				string address = "";
				unsigned short port = 0;
				enum TCPIPConnectionType Type;
				
				string type = Arguments[i+2];
				
				TCPIPOutputSpikeDriver * Driver;
				
				if (type == string("Server")){
					istringstream ss(host);
					if (!(ss >> port)){
	     				throw ParameterException(Arguments[i], "Invalid connection port");
					}
					Type = SERVER;

				}else if (type == string("Client")){
					string::size_type pos = host.find(":",0);
					if ( pos != string::npos ){
						address = host.substr(0, pos);
						istringstream ss(host.substr(pos+1));
						if (!(ss >> port))
	     					throw ParameterException(Arguments[i], "Invalid connection port");
					} else {
						// Output error
						throw ParameterException(Arguments[i],"Invalid output connection. Check the address and the port.");	
					}
					Type = CLIENT;
				} else {
					// Output error
					throw ParameterException(Arguments[i],"Invalid output connection type. Only Server and Client are allowed");
				}
				
				Driver = new TCPIPOutputSpikeDriver (Type,address,port);

				i += 2;
				
				this->OutputDrivers.push_back(Driver);
			} else {
				throw ParameterException(Arguments[i],"Invalid output connection.");
			}
		} else if (CurrentArgument=="-ioc"){
			if (i+2<Number){
				string host = Arguments[i+1];
				string address = "";
				unsigned short port = 0;
				enum TCPIPConnectionType Type;
				
				string type = Arguments[i+2];
				
				TCPIPInputOutputSpikeDriver * Driver;
				
				if (type == string("Server")){
					istringstream ss(host);
					if (!(ss >> port)){
	     				throw ParameterException(Arguments[i], "Invalid connection port");
					}
					Type = SERVER;
				}else if (type == string("Client")){
					string::size_type pos = host.find(":",0);
					if ( pos != string::npos ){
						address = host.substr(0, pos);
						istringstream ss(host.substr(pos+1));
						if (!(ss >> port))
	     					throw ParameterException(Arguments[i], "Invalid connection port");
					} else {
						// Output error
						throw ParameterException(Arguments[i],"Invalid input-output connection. Check the address and the port.");	
					}
					Type = CLIENT;
				} else {
					// Output error
					throw ParameterException(Arguments[i],"Invalid input-output connection type. Only Server and Client are allowed");
				}
				
				Driver = new TCPIPInputOutputSpikeDriver (Type,address,port);

				i += 2;
				
				this->InputDrivers.push_back(Driver);
				this->OutputDrivers.push_back(Driver);
			} else {
				throw ParameterException(Arguments[i],"Invalid input-output connection.");
			}
		} else if(CurrentArgument=="-openmpQ"){
			#ifdef _OPENMP
				if (i+1<Number){
					// Check if it is a number
					istringstream Argument(Arguments[++i]);
	   
   					if (!(Argument >> this->NumberOfQueues) || NumberOfQueues<1)
     					throw ParameterException(Arguments[i], "Invalid number of OpenMP thread");	

					if(NumberOfQueues>omp_get_max_threads()){
						NumberOfQueues=omp_get_max_threads();
					}
				} else {
					throw ParameterException(Arguments[i],"Invalid number of OpenMP thread");				
				}
			#else	
				cout<<"WARNING: OPENMP NO AVAILABLE IN THIS SIMULATION"<<endl;
				NumberOfThreads=1;
				NumberOfQueues=1;
			#endif

		} 	else if(CurrentArgument=="-openmp"){
			#ifdef _OPENMP
				if (i+1<Number){
					// Check if it is a number
					istringstream Argument(Arguments[++i]);
	   
   					if (!(Argument >> this->NumberOfThreads) || NumberOfThreads<1)
     					throw ParameterException(Arguments[i], "Invalid number of OpenMP thread");	

					if(NumberOfThreads>omp_get_max_threads()){
						NumberOfThreads=omp_get_max_threads();
					}
				} else {
					throw ParameterException(Arguments[i],"Invalid number of OpenMP thread");				
				}
			#else	
				cout<<"WARNING: OPENMP NO AVAILABLE IN THIS SIMULATION"<<endl;
				NumberOfThreads=1;
				NumberOfQueues=1;
			#endif

		} else if(CurrentArgument=="-rtgap"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->rtgap))
     				throw ParameterException(Arguments[i], "Invalid real time gap");
			} else {
				throw ParameterException(Arguments[i],"Invalid real time gap");				
			}
		}
		else if(CurrentArgument=="-rt1"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->rt1))
     				throw ParameterException(Arguments[i], "Invalid first real time factor");
			} else {
				throw ParameterException(Arguments[i],"Invalid first real time factor");				
			}		
		}
		else if(CurrentArgument=="-rt2"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->rt2))
     				throw ParameterException(Arguments[i], "Invalid second real time factor");
			} else {
				throw ParameterException(Arguments[i],"Invalid second real time factor");				
			}	
		}
		else if(CurrentArgument=="-rt3"){
			if (i+1<Number){
				// Check if it is a number
				istringstream Argument(Arguments[++i]);
   
   				if (!(Argument >> this->rt3))
     				throw ParameterException(Arguments[i], "Invalid third real time factor");
			} else {
				throw ParameterException(Arguments[i],"Invalid third real time factor");				
			}	
		}
		else if(CurrentArgument=="-rt"){
			this->RealTimeOption=true;
		}
		else {
				throw ParameterException(Arguments[i],"Invalid parameter.");
		}	
	}	
	
	//We check the number of OpenMP threads is higher or equal to the number of queues.
	if(NumberOfThreads<NumberOfQueues){
		NumberOfThreads=NumberOfQueues;
	}

	if(SimulationStepTime>rtgap){
		rtgap=SimulationStepTime;
	}

	if (this->SimulationTime==-1.0){
		throw ParameterException(Arguments[0],"The simulation time isn't specified."); 	
	} else if (this->NetworkFile==NULL){
		throw ParameterException(Arguments[0],"The network configuration file isn't specified.");
	} else if (this->WeightsFile==NULL){
		throw ParameterException(Arguments[0],"The weight configuration file isn't specified.");
	}
	
	return;
}

bool ParamReader::FileExists(string Name){
	bool flag = false;
	fstream fin;
	fin.open(Name.c_str(),ios::in);
	if ( fin.is_open() ){
		flag=true;
	}
	fin.close();
	return flag;	
}
 		 
ParamReader::ParamReader(int ArgNumber, char ** Arg) throw (ParameterException, ConnectionException) :SimulationTime(-1.0), NetworkFile(NULL), WeightsFile(NULL), WeightTime(0.0), NetworkInfo(false),
	SimulationStepTime(0.0), InputDrivers(), OutputDrivers(), OutputWeightDrivers(), NumberOfThreads(1), NumberOfQueues(1), rtgap(0.0f), rt1(1.0f), rt2(1.0f), rt3(1.0f), RealTimeOption(false) {
	ParseArguments(ArgNumber,Arg);	
}
 		
double ParamReader::GetSimulationTime(){
	return this->SimulationTime;	
}

double ParamReader::GetSaveWeightStepTime(){
	return this->WeightTime;
}
 		
char * ParamReader::GetNetworkFile(){
	return this->NetworkFile;
}
 		
char * ParamReader::GetWeightsFile(){
	return this->WeightsFile;
}
 		
bool ParamReader::CheckInfo(){
	return this->NetworkInfo;
}
 				
double ParamReader::GetSimulationStepTime(){
	return this->SimulationStepTime;	
}

int ParamReader::GetNumberOfThreads(){
	return this->NumberOfThreads;
}

int ParamReader::GetNumberOfQueues(){
	return this->NumberOfQueues;
}

float ParamReader::GetRtGap(){
	return this->rtgap;
}

float ParamReader::GetRt1(){
	return this->rt1;
}

float ParamReader::GetRt2(){
	return this->rt2;
}

float ParamReader::GetRt3(){
	return this->rt3;
}

bool ParamReader::GetRealTimeOption(){
	return this->RealTimeOption;
}

 		
vector<InputSpikeDriver *> ParamReader::GetInputSpikeDrivers(){
	return this->InputDrivers;
}
 		
vector<OutputSpikeDriver *> ParamReader::GetOutputSpikeDrivers(){
	return this->OutputDrivers;
}

vector<OutputSpikeDriver *> ParamReader::GetMonitorDrivers(){
	return this->MonitorDrivers;
}

vector<OutputWeightDriver *> ParamReader::GetOutputWeightDrivers(){
	return this->OutputWeightDrivers;
}


//REVIEW THIS FUNCTION.
Simulation * ParamReader::CreateAndInitializeSimulation() throw (EDLUTException, ConnectionException){
cout<<"REVIEW ParamReader::CreateAndInitializeSimulation"<<endl;
	Simulation * Simul = NULL;

	Simul = new Simulation(this->GetNetworkFile(),
                         this->GetWeightsFile(),
                         this->GetSimulationTime(),
                         this->GetSimulationStepTime(),
						 this->GetNumberOfQueues(),
						 this->GetNumberOfThreads());

	Simul->SetSaveStep(this->GetSaveWeightStepTime());

	for (unsigned int i=0; i<this->GetInputSpikeDrivers().size(); ++i){
		Simul->AddInputSpikeDriver(this->GetInputSpikeDrivers()[i]);
	}

	for (unsigned int i=0; i<this->GetOutputSpikeDrivers().size(); ++i){
		Simul->AddOutputSpikeDriver(this->GetOutputSpikeDrivers()[i]);
	}

	for (unsigned int i=0; i<this->GetMonitorDrivers().size(); ++i){
		Simul->AddMonitorActivityDriver(this->GetMonitorDrivers()[i]);
	}

	for (unsigned int i=0; i<this->GetOutputWeightDrivers().size(); ++i){
		Simul->AddOutputWeightDriver(this->GetOutputWeightDrivers()[i]);
	}

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

	// Reset total spike counter
    Simul->SetTotalSpikeCounter(0,0); /*asdfgf*/

	// Get the external initial inputs
	Simul->InitSimulation();

	return Simul;
}
