/***************************************************************************
 *                           Network_GPU.cpp                               *
 *                           -------------------                           *
 * copyright            : (C) 2012 by Jesus Garrido, Richard Carrillo and  *
 *						: Francisco Naveros                                *
 * email                : jgarrido@atc.ugr.es, fnaveros@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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * \note: this file Network_GPU.cpp must be used instead of file Network.cpp to 
 * implement a CPU-GPU hybrid architecture.
*/


#include "../../include/spike/Network.h"
#include "../../include/spike/Interconnection.h"
#include "../../include/spike/Neuron.h"
#include "../../include/spike/InternalSpike.h"

#include "../../include/learning_rules/ExpWeightChange.h"
#include "../../include/learning_rules/SinWeightChange.h"
#include "../../include/learning_rules/CosWeightChange.h"
#include "../../include/learning_rules/SimetricCosWeightChange.h"
#include "../../include/learning_rules/STDPWeightChange.h"
#include "../../include/learning_rules/STDPLSWeightChange.h"
#include "../../include/learning_rules/SimetricCosSTDPWeightChange.h"
#include "../../include/learning_rules/SimetricCosSinWeightChange.h"
#include "../../include/learning_rules/SimetricCosSinSTDPWeightChange.h"

#include "../../include/neuron_model/NeuronModel.h"
#include "../../include/neuron_model/SRMTimeDrivenModel.h"
#include "../../include/neuron_model/LIFTimeDrivenModel_1_2.h"
#include "../../include/neuron_model/LIFTimeDrivenModel_1_4.h"
#include "../../include/neuron_model/TimeDrivenNeuronModel.h"
#include "../../include/neuron_model/EventDrivenNeuronModel.h"
#include "../../include/neuron_model/TableBasedModel.h"
#include "../../include/neuron_model/TableBasedModelHF.h"
#include "../../include/neuron_model/SRMTableBasedModel.h"
#include "../../include/neuron_model/VectorNeuronState.h"
#include "../../include/neuron_model/EgidioGranuleCell_TimeDriven.h"
#include "../../include/neuron_model/Vanderpol.h"
#include "../../include/neuron_model/TimeDrivenPurkinjeCell.h"

#include "../../include/neuron_model/TimeDrivenNeuronModel_GPU.h"
#include "../../include/neuron_model/LIFTimeDrivenModel_1_4_GPU.h"
#include "../../include/neuron_model/LIFTimeDrivenModel_1_2_GPU.h"
#include "../../include/neuron_model/EgidioGranuleCell_TimeDriven_GPU.h"

#include "../../include/simulation/EventQueue.h"
#include "../../include/simulation/Utils.h"
#include "../../include/simulation/Configuration.h"

#include "../../include/cudaError.h"

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

#include <iostream>
#include <string>
#include <cmath>
using namespace std;


int qsort_inters(const void *e1, const void *e2){
	int ord;
	double ordf;
	
	//Calculate source index
	ord=((Interconnection *)e1)->GetTarget()->get_OpenMP_queue_index() - ((Interconnection *)e2)->GetTarget()->get_OpenMP_queue_index();
	if(!ord){
		//the same source index-> calculate target OpenMP index
		ord=((Interconnection *)e1)->GetSource()->GetIndex() - ((Interconnection *)e2)->GetSource()->GetIndex();
		if(!ord){
			ordf=((Interconnection *)e1)->GetDelay() - ((Interconnection *)e2)->GetDelay();
			if(ordf<0.0){
				ord=-1;
			}else if(ordf>0.0){
				ord=1;
			}//The same propagation time-> calculate targe index
			else if(ordf==0){
				ord=((Interconnection *)e1)->GetTarget()->GetIndex() - ((Interconnection *)e2)->GetTarget()->GetIndex();
			}
		}
	}
   
	return(ord);
}

void Network::FindOutConnections(){
	// Change the ordenation
   	qsort(inters,ninters,sizeof(Interconnection),qsort_inters);
	if(ninters>0){
		// Calculate the number of input connections with learning for each cell
		unsigned long ** NumberOfOutputs = (unsigned long **) new unsigned long *[this->nneurons];
		unsigned long ** OutputsLeft = (unsigned long **) new unsigned long *[this->nneurons];
		for (unsigned long i=0; i<this->nneurons; i++){
			NumberOfOutputs[i]=(unsigned long *) new unsigned long [this->GetNumberOfQueues()]();
			OutputsLeft[i]=(unsigned long *) new unsigned long [this->GetNumberOfQueues()];
		}
		


		for (unsigned long con= 0; con<this->ninters; ++con){
			NumberOfOutputs[this->inters[con].GetSource()->GetIndex()][this->inters[con].GetTarget()->get_OpenMP_queue_index()]++;
		}

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			for(int i=0; i<this->GetNumberOfQueues(); i++){
				OutputsLeft[neu][i] = NumberOfOutputs[neu][i];
			}
		}

		Interconnection **** OutputConnections = (Interconnection ****) new Interconnection *** [this->nneurons];
		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			OutputConnections[neu] = (Interconnection ***) new Interconnection ** [this->GetNumberOfQueues()];
		}

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			for(int i=0; i<this->GetNumberOfQueues(); i++){
				if (NumberOfOutputs[neu][i]>0){
					OutputConnections[neu][i] = (Interconnection **) new Interconnection * [NumberOfOutputs[neu][i]];
				} else {
					OutputConnections[neu][i] = 0;
				}
			}
		}

		for (unsigned long con= this->ninters-1; con<this->ninters; --con){
			unsigned long SourceCell = this->inters[con].GetSource()->GetIndex();
			int OpenMP_index=this->inters[con].GetTarget()->get_OpenMP_queue_index();
			OutputConnections[SourceCell][OpenMP_index][--OutputsLeft[SourceCell][OpenMP_index]] = this->inters+con;
		}

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			this->neurons[neu].SetOutputConnections(OutputConnections[neu],NumberOfOutputs[neu]);

			for(int i=0; i<this->GetNumberOfQueues(); i++){	
				for (unsigned long aux = 0; aux < NumberOfOutputs[neu][i]; aux++){
					if(OutputConnections[neu][i][aux]->GetWeightChange_withoutPost()!=0){
						OutputConnections[neu][i][aux]->SetLearningRuleIndex_withoutPost(OutputConnections[neu][i][aux]->GetWeightChange_withoutPost()->counter);
						OutputConnections[neu][i][aux]->GetWeightChange_withoutPost()->counter++;
					}
				}
			}
		}

		delete [] OutputConnections;
		delete [] NumberOfOutputs;
		delete [] OutputsLeft;
	}
}

void Network::SetWeightOrdination(){
	if (ninters>0){
		for (int ninter=0; ninter<ninters;ninter++){
			int index = inters[ninter].GetIndex();
			this->wordination[index] = &(this->inters[ninter]);
		}
	}	
}

void Network::FindInConnections(){
	if(this->ninters>0){

		// Calculate the number of input connections with learning for each cell
		unsigned long * NumberOfInputsWithPostSynapticLearning = (unsigned long *) new unsigned long [this->nneurons]();
		unsigned long * InputsLeftWithPostSynapticLearning = (unsigned long *) new unsigned long [this->nneurons];

		unsigned long * NumberOfInputsWithoutPostSynapticLearning = (unsigned long *) new unsigned long [this->nneurons]();
		unsigned long * InputsLeftWithoutPostSynapticLearning = (unsigned long *) new unsigned long [this->nneurons];

		for (unsigned long con= 0; con<this->ninters; ++con){
			if(this->inters[con].GetWeightChange_withPost()!=0){
				NumberOfInputsWithPostSynapticLearning[this->inters[con].GetTarget()->GetIndex()]++;
			}
			if(this->inters[con].GetWeightChange_withoutPost()!=0){
				NumberOfInputsWithoutPostSynapticLearning[this->inters[con].GetTarget()->GetIndex()]++;
			}
		}

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			InputsLeftWithPostSynapticLearning[neu] = NumberOfInputsWithPostSynapticLearning[neu];
			InputsLeftWithoutPostSynapticLearning[neu] = NumberOfInputsWithoutPostSynapticLearning[neu];
		}

		Interconnection *** InputConnectionsWithPostSynapticLearning = (Interconnection ***) new Interconnection ** [this->nneurons];
		Interconnection *** InputConnectionsWithoutPostSynapticLearning = (Interconnection ***) new Interconnection ** [this->nneurons];

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			InputConnectionsWithPostSynapticLearning[neu] = 0;
			InputConnectionsWithoutPostSynapticLearning[neu] = 0;

			if (NumberOfInputsWithPostSynapticLearning[neu]>0){
				InputConnectionsWithPostSynapticLearning[neu] = (Interconnection **) new Interconnection * [NumberOfInputsWithPostSynapticLearning[neu]];
			} 
			if (NumberOfInputsWithoutPostSynapticLearning[neu]>0){
				InputConnectionsWithoutPostSynapticLearning[neu] = (Interconnection **) new Interconnection * [NumberOfInputsWithoutPostSynapticLearning[neu]];
			} 
		}

		for (unsigned long con= this->ninters-1; con<this->ninters; --con){
			if (this->inters[con].GetWeightChange_withPost()!=0){
				unsigned long TargetCell = this->inters[con].GetTarget()->GetIndex();
				InputConnectionsWithPostSynapticLearning[TargetCell][--InputsLeftWithPostSynapticLearning[TargetCell]] = this->inters+con;
			}
			if (this->inters[con].GetWeightChange_withoutPost()!=0){
				unsigned long TargetCell = this->inters[con].GetTarget()->GetIndex();
				InputConnectionsWithoutPostSynapticLearning[TargetCell][--InputsLeftWithoutPostSynapticLearning[TargetCell]] = this->inters+con;
			}
		}

		for (unsigned long neu = 0; neu<this->nneurons; ++neu){
			this->neurons[neu].SetInputConnectionsWithPostSynapticLearning(InputConnectionsWithPostSynapticLearning[neu],NumberOfInputsWithPostSynapticLearning[neu]);
			this->neurons[neu].SetInputConnectionsWithoutPostSynapticLearning(InputConnectionsWithoutPostSynapticLearning[neu],NumberOfInputsWithoutPostSynapticLearning[neu]);
		
			for (unsigned long aux = 0; aux < NumberOfInputsWithPostSynapticLearning[neu]; aux++){
				InputConnectionsWithPostSynapticLearning[neu][aux]->SetLearningRuleIndex_withPost(InputConnectionsWithPostSynapticLearning[neu][aux]->GetWeightChange_withPost()->counter);
				InputConnectionsWithPostSynapticLearning[neu][aux]->GetWeightChange_withPost()->counter++;
			}

		}

		delete [] InputConnectionsWithPostSynapticLearning;
		delete [] NumberOfInputsWithPostSynapticLearning;
		delete [] InputsLeftWithPostSynapticLearning;

		delete [] InputConnectionsWithoutPostSynapticLearning;
		delete [] NumberOfInputsWithoutPostSynapticLearning;
		delete [] InputsLeftWithoutPostSynapticLearning;
	}
}



NeuronModel ** Network::LoadNetTypes(string ident_type, string neutype, int & ni) throw (EDLUTException){
	NeuronModel ** type;

   	for(ni=0;ni<nneutypes && neutypes[ni][0]!=0 && ( neutypes[ni][0]->GetModelID()==neutype && neutypes[ni][0]->GetTypeID()!=ident_type || neutypes[ni][0]->GetModelID()!=neutype);++ni);
	
	if (ni<nneutypes && neutypes[ni][0]==0){
		for(int n=0; n<this->GetNumberOfQueues(); n++){
			if (ident_type=="LIFTimeDrivenModel_1_4"){
				neutypes[ni][n] = (LIFTimeDrivenModel_1_4 *) new LIFTimeDrivenModel_1_4(ident_type, neutype);
			}else if (ident_type=="LIFTimeDrivenModel_1_2"){
				neutypes[ni][n] = (LIFTimeDrivenModel_1_2 *) new LIFTimeDrivenModel_1_2(ident_type, neutype);
			}else if (ident_type=="SRMTimeDrivenModel"){
				neutypes[ni][n] = (SRMTimeDrivenModel *) new SRMTimeDrivenModel(ident_type, neutype);
			}else if (ident_type=="TableBasedModelHF"){
				neutypes[ni][n] = (TableBasedModel *) new TableBasedModelHF(ident_type, neutype);
			}else if (ident_type=="TableBasedModel"){
				neutypes[ni][n] = (TableBasedModel *) new TableBasedModel(ident_type, neutype);
			} else if (ident_type=="SRMTableBasedModel"){
				neutypes[ni][n] = (SRMTableBasedModel *) new SRMTableBasedModel(ident_type, neutype);
			} else if (ident_type=="EgidioGranuleCell_TimeDriven"){
				neutypes[ni][n] = (EgidioGranuleCell_TimeDriven *) new EgidioGranuleCell_TimeDriven(ident_type, neutype);
			}else if (ident_type=="Vanderpol"){
				neutypes[ni][n] = (Vanderpol *) new Vanderpol(ident_type, neutype);
			}else if (ident_type=="TimeDrivenPurkinjeCell"){
				neutypes[ni][n] = (TimeDrivenPurkinjeCell *) new TimeDrivenPurkinjeCell(ident_type, neutype);
			}else if (ident_type=="LIFTimeDrivenModel_1_2_GPU"){
				if(NumberOfGPUs>0){
					neutypes[ni][n] = (LIFTimeDrivenModel_1_2_GPU *) new LIFTimeDrivenModel_1_2_GPU(ident_type, neutype);
				}else{
					printf("WARNING: CUDA capable GPU not available. Implementing CPU model intead of GPU model\n");
					neutypes[ni][n] = (LIFTimeDrivenModel_1_2 *) new LIFTimeDrivenModel_1_2(ident_type, neutype);
				}
			}else if (ident_type=="LIFTimeDrivenModel_1_4_GPU"){
				if(NumberOfGPUs>0){
					neutypes[ni][n] = (LIFTimeDrivenModel_1_4_GPU *) new LIFTimeDrivenModel_1_4_GPU(ident_type, neutype);
				}else{
					printf("WARNING: CUDA capable GPU not available. Implementing CPU model intead of GPU model\n");
					neutypes[ni][n] = (LIFTimeDrivenModel_1_4 *) new LIFTimeDrivenModel_1_4(ident_type, neutype);
				}
			}else if (ident_type=="EgidioGranuleCell_TimeDriven_GPU"){
				if(NumberOfGPUs>0){
					neutypes[ni][n] = (EgidioGranuleCell_TimeDriven_GPU *) new EgidioGranuleCell_TimeDriven_GPU(ident_type, neutype);
				}else{
					printf("WARNING: CUDA capable GPU not available. Implementing CPU model intead of GPU model\n");
					neutypes[ni][n] = (EgidioGranuleCell_TimeDriven *) new EgidioGranuleCell_TimeDriven(ident_type, neutype);
				}
			}else {
				throw EDLUTException(13,58,30,0);
			}
			neutypes[ni][n]->LoadNeuronModel();
		}
		type=neutypes[ni];
	} else if (ni<nneutypes) {
		type = neutypes[ni];
	} else {
		throw EDLUTException(13,44,20,0);
	}

	return(type);
}


void Network::InitializeStates(int ** N_neurons){
	for( int z=0; z< this->nneutypes; z++){
		for(int j=0; j<this->GetNumberOfQueues(); j++){
			if(N_neurons[z][j]>0){
				neutypes[z][j]->InitializeStates(N_neurons[z][j], j);
			}else{
				neutypes[z][j]->InitializeStates(1,j);
			}
		}
	}
}



void Network::InitNetPredictions(EventQueue * Queue){
	int nneu;
	for(nneu=0;nneu<nneurons;nneu++){
		if (neurons[nneu].GetNeuronModel()->GetModelType()==EVENT_DRIVEN_MODEL){
			EventDrivenNeuronModel * Model = (EventDrivenNeuronModel *) neurons[nneu].GetNeuronModel();
			InternalSpike * spike = Model->GenerateInitialActivity(neurons+nneu);
			if (spike!=0){
				Queue->InsertEvent(spike->GetSource()->get_OpenMP_queue_index(),spike);
			}
		}
	}

}

Network::Network(const char * netfile, const char * wfile, EventQueue * Queue, int numberOfQueues) throw (EDLUTException): inters(0), ninters(0), neutypes(0), nneutypes(0), neurons(0), nneurons(0), timedrivenneurons(0), ntimedrivenneurons(0), wchanges(0), nwchanges(0), wordination(0), NumberOfQueues(numberOfQueues), minpropagationdelay(0.0001), invminpropagationdelay(1.0/0.0001){
	this->LoadNet(netfile);	
	this->LoadWeights(wfile);
	this->InitNetPredictions(Queue);	
}
   		
Network::~Network(){
	if (inters!=0) {
		delete [] inters;
	}

	if (neutypes!=0) {
		for (int i=0; i<this->nneutypes; ++i){
			if (this->neutypes[i]!=0){
				for( int j=0; j<this->GetNumberOfQueues(); j++){
					if (this->neutypes[i][j]!=0){
						if(ntimedrivenneurons_GPU[i][0]>0){
							HANDLE_ERROR(cudaSetDevice(GPUsIndex[j % NumberOfGPUs]));
						}
						delete this->neutypes[i][j];
					}
				}
				delete [] this->neutypes[i];
			}
		}
		delete [] neutypes;
	}

	
	if (neurons!=0) {
		delete [] neurons;
	}

	if (timedrivenneurons!=0) {
		for (int z=0; z<this->nneutypes; z++){
			if(timedrivenneurons[z]!=0){
				for(int j=0; j<this->GetNumberOfQueues(); j++){
					delete [] timedrivenneurons[z][j];
				}
				delete [] timedrivenneurons[z];
			}
		}
		delete [] timedrivenneurons;
	}

	if(ntimedrivenneurons!=0){
		for(int i=0; i<this->nneutypes; i++){
			if(ntimedrivenneurons[i]!=0){
				delete [] ntimedrivenneurons[i];
			}
		}
		delete [] ntimedrivenneurons;
	}

	if (timedrivenneurons_GPU!=0) {
		for (int z=0; z<this->nneutypes; z++){
			if(timedrivenneurons_GPU[z]!=0){
				for(int j=0; j<this->GetNumberOfQueues(); j++){
					delete [] timedrivenneurons_GPU[z][j];
				}
				delete [] timedrivenneurons_GPU[z];
			}
		}
		delete [] timedrivenneurons_GPU;
	}

	if(ntimedrivenneurons_GPU!=0){
		for(int i=0; i<this->nneutypes; i++){
			if(ntimedrivenneurons_GPU[i]!=0){
				delete [] ntimedrivenneurons_GPU[i];
			}
		}
		delete [] ntimedrivenneurons_GPU;
	}

	if (wchanges!=0) {
		for (int i=0; i<this->nwchanges; ++i){
			delete this->wchanges[i];
		}
		delete [] wchanges;
	}
	if (wordination!=0) delete [] wordination;
}
   		
Neuron * Network::GetNeuronAt(int index) const{
	return &(this->neurons[index]);
}
   		
int Network::GetNeuronNumber() const{
	return this->nneurons;	
}

Neuron ** Network::GetTimeDrivenNeuronAt(int index0,int index1) const{
	return this->timedrivenneurons[index0][index1];
}

Neuron * Network::GetTimeDrivenNeuronAt(int index0,int index1, int index2) const{
	return this->timedrivenneurons[index0][index1][index2];
}

Neuron ** Network::GetTimeDrivenNeuronGPUAt(int index0, int index1) const{
	return this->timedrivenneurons_GPU[index0][index1];
}
Neuron * Network::GetTimeDrivenNeuronGPUAt(int index0,int index1, int index2) const{
	return this->timedrivenneurons_GPU[index0][index1][index2];
}
   		
int ** Network::GetTimeDrivenNeuronNumber() const{
	return this->ntimedrivenneurons;
}

int Network::GetNneutypes() const{
	return this->nneutypes;
}
int ** Network::GetTimeDrivenNeuronNumberGPU() const{
	return this->ntimedrivenneurons_GPU;
}

NeuronModel ** Network::GetNeuronModelAt(int index) const{
	return this->neutypes[index];
}

NeuronModel * Network::GetNeuronModelAt(int index1, int index2) const{
	return this->neutypes[index1][index2];
}

LearningRule * Network::GetLearningRuleAt(int index) const{
	return this->wchanges[index];
}

int Network::GetLearningRuleNumber() const{
	return this->nwchanges;
}

void Network::LoadNet(const char *netfile) throw (EDLUTException){
	FILE *fh;
	long savedcurrentline;
	long Currentline=1L;
	fh=fopen(netfile,"rt");
	if(fh){
		skip_comments(fh, Currentline);
		if(fscanf(fh,"%i",&(this->nneutypes))==1){
			this->neutypes=(NeuronModel ***) new NeuronModel ** [this->nneutypes];
			if(this->neutypes){
				for(int ni=0;ni<this->nneutypes;ni++){
					this->neutypes[ni]=(NeuronModel **) new NeuronModel * [this->GetNumberOfQueues()];
					for(int n=0; n<this->GetNumberOfQueues(); n++){
						this->neutypes[ni][n]=0;
					}
				}
            	skip_comments(fh, Currentline);
            	if(fscanf(fh,"%i",&(this->nneurons))==1){
            		int tind,nind,nn,outn,monit;
            		NeuronModel ** type;
            		char ident[MAXIDSIZE+1];
            		char ident_type[MAXIDSIZE+1];
            		this->neurons=(Neuron *) new Neuron [this->nneurons];

					ntimedrivenneurons= (int**) new int* [this->nneutypes]();
					int *** time_driven_index = (int ***) new int **[this->nneutypes];
							
					ntimedrivenneurons_GPU= (int **) new int* [this->nneutypes]();
					int *** time_driven_index_GPU=(int ***) new int **[this->nneutypes];

					int ** N_neurons= (int **) new int *[this->nneutypes]();

					for (int z=0; z<this->nneutypes; z++){
						ntimedrivenneurons[z]=new int [this->GetNumberOfQueues()]();
						ntimedrivenneurons_GPU[z]=new int [this->GetNumberOfQueues()]();

						time_driven_index[z]=(int**)new int* [this->GetNumberOfQueues()];
						time_driven_index_GPU[z]=(int**)new int* [this->GetNumberOfQueues()];
						for(int j=0; j<this->GetNumberOfQueues(); j++){
							time_driven_index[z][j]=new int [this->nneurons]();
							time_driven_index_GPU[z][j]=new int [this->nneurons]();
						}

						N_neurons[z]=new int [this->GetNumberOfQueues()]();
					}

					


            		if(this->neurons){
            			for(tind=0;tind<this->nneurons;tind+=nn){
                     		skip_comments(fh,Currentline);
                     		if(fscanf(fh,"%i",&nn)==1 && fscanf(fh," %"MAXIDSIZEC"[^ ]%*[^ ]",ident_type)==1 && fscanf(fh," %"MAXIDSIZEC"[^ ]%*[^ ]",ident)==1 && fscanf(fh,"%i",&outn)==1 && fscanf(fh,"%i",&monit)==1){
                     			if(tind+nn>this->nneurons){
                     				throw EDLUTFileException(4,7,6,1,Currentline);
                     			}
								int ni;                    
                        		savedcurrentline=Currentline;
                        		type=LoadNetTypes(ident_type, ident, ni);
                        		Currentline=savedcurrentline;

								int blockSize=(nn + NumberOfQueues - 1) / NumberOfQueues;
								int blockIndex, threadIndex;
	                    		for(nind=0;nind<nn;nind++){
									blockIndex=nind/blockSize;
									threadIndex=blockIndex;

									neurons[nind+tind].InitNeuron(nind+tind, N_neurons[ni][blockIndex], type[blockIndex],bool(monit), bool(outn), threadIndex);
									N_neurons[ni][blockIndex]++;

									//If some neuron is monitored.
									if(monit){
										for(int n=0; n<this->GetNumberOfQueues(); n++){
											type[n]->GetVectorNeuronState()->Set_Is_Monitored(true);
										}
									}
									
									if (type[0]->GetModelType()==TIME_DRIVEN_MODEL_CPU){
										time_driven_index[ni][blockIndex][this->ntimedrivenneurons[ni][blockIndex]] = nind+tind;
										this->ntimedrivenneurons[ni][blockIndex]++;
									}
											
									if (type[0]->GetModelType()==TIME_DRIVEN_MODEL_GPU){
										time_driven_index_GPU[ni][blockIndex][this->ntimedrivenneurons_GPU[ni][blockIndex]]=nind+tind;
										this->ntimedrivenneurons_GPU[ni][blockIndex]++;
									}
                        		}
                        	}else{
                        		throw EDLUTFileException(4,8,7,1,Currentline);
                        	}
                     	}

						// Create the time-driven cell array
						timedrivenneurons=(Neuron ****) new Neuron *** [this->nneutypes]();
						for (int z=0; z<this->nneutypes; z++){ 
							if (this->ntimedrivenneurons[z][0]>0){
								this->timedrivenneurons[z]=(Neuron ***) new Neuron ** [this->GetNumberOfQueues()];
								for(int j=0; j<this->GetNumberOfQueues(); j++){
									this->timedrivenneurons[z][j]=(Neuron **) new Neuron * [this->ntimedrivenneurons[z][j]];
									for (int i=0; i<this->ntimedrivenneurons[z][j]; ++i){
										this->timedrivenneurons[z][j][i] = &(this->neurons[time_driven_index[z][j][i]]);
									}	
								}
							}
						}

						// Create the time-driven cell array for GPU
						timedrivenneurons_GPU=(Neuron ****) new Neuron *** [this->nneutypes]();
						for (int z=0; z<this->nneutypes; z++){ 
							if (this->ntimedrivenneurons_GPU[z][0]>0){
								this->timedrivenneurons_GPU[z]=(Neuron ***) new Neuron ** [this->GetNumberOfQueues()];
								for(int j=0; j<this->GetNumberOfQueues(); j++){
									this->timedrivenneurons_GPU[z][j]=(Neuron **) new Neuron * [this->ntimedrivenneurons_GPU[z][j]];
									for (int i=0; i<this->ntimedrivenneurons_GPU[z][j]; ++i){
										this->timedrivenneurons_GPU[z][j][i] = &(this->neurons[time_driven_index_GPU[z][j][i]]);
									}	
								}
							}
						}

						// Initialize states. 
						InitializeStates(N_neurons);

            		}else{
            			throw EDLUTFileException(4,5,28,0,Currentline);
            		}

					

					for (int z=0; z<this->nneutypes; z++){
						for(int j=0; j<this->GetNumberOfQueues(); j++){
							delete [] time_driven_index[z][j];
							delete [] time_driven_index_GPU[z][j];
						}
						delete [] time_driven_index[z];
						delete [] time_driven_index_GPU[z];
						delete [] N_neurons[z];
					} 
					delete [] time_driven_index;
					delete [] time_driven_index_GPU;
					delete [] N_neurons;

            		/////////////////////////////////////////////////////////
            		// Check the number of neuron types
					int ni;
            		for(ni=0;ni<this->nneutypes && this->neutypes[ni]!=0;ni++);

            		if (ni!=this->nneutypes){
            			throw EDLUTException(13,44,20,0);
            		}
            	}else{
            		throw EDLUTFileException(4,9,8,1,Currentline);
            	}
            	
            	
            	skip_comments(fh,Currentline);
            	int * N_ConectionWithLearning;
        		if(fscanf(fh,"%i",&(this->nwchanges))==1){
        			int wcind;
        			this->wchanges=new LearningRule * [this->nwchanges];
        			N_ConectionWithLearning=new int [this->nwchanges](); 
        			if(this->wchanges){
        				for(wcind=0;wcind<this->nwchanges;wcind++){
        					char ident_type[MAXIDSIZE+1];
        					skip_comments(fh,Currentline);
        					string LearningModel;
        					if(fscanf(fh," %"MAXIDSIZEC"[^ ]%*[^ ]",ident_type)==1){
        						if (string(ident_type)==string("ExpAdditiveKernel")){
        							this->wchanges[wcind] = new ExpWeightChange(wcind);
        						} else if (string(ident_type)==string("SinAdditiveKernel")){
        							this->wchanges[wcind] = new SinWeightChange(wcind);
        						} else if (string(ident_type)==string("CosAdditiveKernel")){
									this->wchanges[wcind] = new CosWeightChange(wcind);
        						} else if (string(ident_type)==string("SimetricCosAdditiveKernel")){
									this->wchanges[wcind] = new SimetricCosWeightChange(wcind);
								} else if (string(ident_type)==string("SimetricCosSinSTDPAdditiveKernel")){
									this->wchanges[wcind] = new SimetricCosSinSTDPWeightChange(wcind);
									N_ConectionWithLearning[wcind]=this->GetNeuronNumber();
									this->wchanges[wcind]->counter=this->GetNeuronNumber();
        						} else if (string(ident_type)==string("SimetricCosSinAdditiveKernel")){
									this->wchanges[wcind] = new SimetricCosSinWeightChange(wcind);
        						} else if (string(ident_type)==string("STDP")){
        							this->wchanges[wcind] = new STDPWeightChange(wcind);
        						} else if (string(ident_type)==string("STDPLS")){
        							this->wchanges[wcind] = new STDPLSWeightChange(wcind);
        						} else if (string(ident_type)==string("SimetricCosSTDPAdditiveKernel")){
        							this->wchanges[wcind] = new SimetricCosSTDPWeightChange(wcind);
									N_ConectionWithLearning[wcind]=this->GetNeuronNumber();
									this->wchanges[wcind]->counter=this->GetNeuronNumber();
        						} else {
                           			throw EDLUTFileException(4,28,23,1,Currentline);
        						}

        						this->wchanges[wcind]->LoadLearningRule(fh,Currentline);

                       		}else{
                       			throw EDLUTFileException(4,28,23,1,Currentline);
                       		}
        				}
        			}else{
        				throw EDLUTFileException(4,5,4,0,Currentline);
        			}
        		}else{
        			throw EDLUTFileException(4,26,21,1,Currentline);
        		}

        		skip_comments(fh,Currentline);
        		if(fscanf(fh,"%li",&(this->ninters))==1){
        			int source,nsources,target,ntargets,nreps;
					char wchange1[80];
					char wchange2[80];
					char triggerchar='t';
					int intwchange1, intwchange2;
					bool trigger1, trigger2;

        			float delay,delayinc,maxweight;
        			int type;
        			int iind,sind,tind,rind,posc;
        			this->inters=(Interconnection *) new Interconnection [this->ninters];
        			this->wordination=(Interconnection **) new Interconnection * [this->ninters];
        			if(this->inters && this->wordination){
        				for(iind=0;iind<this->ninters;iind+=nsources*ntargets*nreps){
        					skip_comments(fh,Currentline);
        					if(fscanf(fh,"%i",&source)==1 && fscanf(fh,"%i",&nsources)==1 && fscanf(fh,"%i",&target)==1 && fscanf(fh,"%i",&ntargets)==1 && fscanf(fh,"%i",&nreps)==1 && fscanf(fh,"%f",&delay)==1 && fscanf(fh,"%f",&delayinc)==1 && fscanf(fh,"%i",&type)==1 && fscanf(fh,"%f",&maxweight)==1 && fscanf(fh,"%s",&wchange1)==1){
								trigger1=false;
								int offset1=0;
								if(wchange1[0]==triggerchar){
									trigger1=true;
									offset1=1;
								}
								intwchange1 = atoi(wchange1+offset1);


								intwchange2=-1;
								trigger2=false;
								if(intwchange1>=0){
									if(is_end_line(fh,Currentline)==false){
										if(fscanf(fh,"%s",&wchange2)==1){
											int offset2=0;
											if(wchange2[0]==triggerchar){
												trigger2=true;
												offset2=1;
											}
											intwchange2 = atoi(wchange2+offset2);
															
											if(intwchange2>= this->nwchanges){
  												throw EDLUTFileException(4,29,24,1,Currentline);
											}
										}else{
											throw EDLUTFileException(4,12,11,1,Currentline);
										}
									}
								}
								if(iind+nsources*ntargets*nreps>this->ninters){
        							throw EDLUTFileException(4,10,9,1,Currentline);
        						}else{
        							if(source+nreps*nsources>this->nneurons || target+nreps*ntargets>this->nneurons){
  										throw EDLUTFileException(4,11,10,1,Currentline);
  									}else{
  										if(intwchange1 >= this->nwchanges){
  											throw EDLUTFileException(4,29,24,1,Currentline);
  										}
        							}
        						}

								if(trigger1==false && (intwchange2>=0 && trigger2==false)){
									cout<<"WARNING: Two learning rules will try to change the same synaptic weight in network file line "<<Currentline<<endl;
								}
								if(trigger1==true && trigger2==true){
									cout<<"ERROR: Two trigger learning rules in the same synpase in network file line "<<Currentline<<endl;
								}
        						
        						for(rind=0;rind<nreps;rind++){
        							for(sind=0;sind<nsources;sind++){
        								for(tind=0;tind<ntargets;tind++){
        									posc=iind+rind*nsources*ntargets+sind*ntargets+tind;
        									this->inters[posc].SetIndex(posc);
        									this->inters[posc].SetSource(&(this->neurons[source+rind*nsources+sind]));
        									this->inters[posc].SetTarget(&(this->neurons[target+rind*ntargets+tind]));
        									this->inters[posc].SetDelay(RoundPropagationDelay(delay+delayinc*tind));

											int validType=this->inters[posc].GetTarget()->GetNeuronModel()->CheckSynapseTypeNumber(type);
											if(type!=validType){
												cout<<"synapse "<<posc<<" has a connection type of "<<type<<". It is not supported by the target neuron. It has been set to "<<validType<<endl;
											}
        									this->inters[posc].SetType(validType);
        									this->inters[posc].SetWeight(maxweight);   //TODO: Use max interconnection conductance
   									
											this->inters[posc].SetMaxWeight(maxweight);

											this->inters[posc].SetWeightChange_withPost(0);
											this->inters[posc].SetWeightChange_withoutPost(0);
											if(intwchange1 >= 0){
												//Set the new learning rule
												if(wchanges[intwchange1]->ImplementPostSynaptic()==true){
													this->inters[posc].SetWeightChange_withPost(this->wchanges[intwchange1]);
												}else{
													this->inters[posc].SetWeightChange_withoutPost(this->wchanges[intwchange1]);
													if(trigger1==true){
														this->inters[posc].SetTriggerConnection();
													}
												}
												N_ConectionWithLearning[intwchange1]++;
											}

											if(intwchange2 >= 0){
												//Set the new learning rule
												if(wchanges[intwchange2]->ImplementPostSynaptic()==true){
													this->inters[posc].SetWeightChange_withPost(this->wchanges[intwchange2]);
												}else{
													this->inters[posc].SetWeightChange_withoutPost(this->wchanges[intwchange2]);
													if(trigger2==true){
														this->inters[posc].SetTriggerConnection();
													}
												}
												N_ConectionWithLearning[intwchange2]++;
											}
                                		}
        							}
        						}
        					}else{
        						throw EDLUTFileException(4,12,11,1,Currentline);
        					}
        				}
						for(int t=0; t<this->nwchanges; t++){
							if(N_ConectionWithLearning[t]>0){
								this->wchanges[t]->InitializeConnectionState(N_ConectionWithLearning[t]);
							}
						}
						if(this->nwchanges>0){
						delete [] N_ConectionWithLearning;
						}
        				
						FindOutConnections();
                    	SetWeightOrdination(); // must be before find_in_c() and after find_out_c()
                    	FindInConnections();

						//Calculate the output delay structure of each neuron.
						for(int i=0; i<this->GetNeuronNumber(); i++){
							this->GetNeuronAt(i)->CalculateOutputDelayStructure();
						}
						for(int i=0; i<this->GetNeuronNumber(); i++){
							this->GetNeuronAt(i)->CalculateOutputDelayIndex();
						}
						

                    }else{
        				throw EDLUTFileException(4,5,28,0,Currentline);
        			}
        		}else{
        			throw EDLUTFileException(4,13,12,1,Currentline);
        		}
            }else{
            	throw EDLUTFileException(4,5,4,0,Currentline);
			}
		}else{
			throw EDLUTFileException(4,6,5,1,Currentline);
		}
		
		fclose(fh);
	}else{
		throw EDLUTFileException(4,14,13,0,Currentline);
	}
	
	return;
}

void Network::LoadWeights(const char *wfile) throw (EDLUTFileException){
	FILE *fh;
	int connind;
	long Currentline=1L;
	fh=fopen(wfile,"rt");
	if(fh){
		int nweights,weind;
		float weight;
		skip_comments(fh,Currentline);
		for(connind=0;connind<this->ninters;connind+=nweights){
			skip_comments(fh, Currentline);
			if(fscanf(fh,"%i",&nweights)==1 && fscanf(fh,"%f",&weight)==1){
				if(nweights < 0 || nweights + connind > this->ninters){
					throw EDLUTFileException(11,32,26,1,Currentline);
				}
				
				if(nweights == 0){
					nweights=this->ninters-connind;
				}
				
				for(weind=0;weind<nweights;weind++){
					Interconnection * Connection = this->wordination[connind+weind];
					Connection->SetWeight(((weight < 0.0)?rand()*Connection->GetMaxWeight()/RAND_MAX:((weight > Connection->GetMaxWeight())?Connection->GetMaxWeight():weight)));
				}
			}else{
				throw EDLUTFileException(11,31,25,1,Currentline);
			}
		}
		fclose(fh);
	}else{
		throw EDLUTFileException(11,30,13,0,Currentline);
	}
	
}

void Network::SaveWeights(const char *wfile) throw (EDLUTException){
	FILE *fh;
	int connind;
	fh=fopen(wfile,"wt");
	if(fh){
		float weight,antweight;
		int nantw;
		nantw=0;
		antweight=0.0;
		weight=0.0; // just to avoid compiler warning messages
		
		// Write the number of weights
		//if(fprintf(fh,"%li\n",this->ninters) <= 0){
		//	throw EDLUTException(12,33,4,0);
		//}
					
		for(connind=0;connind<=this->ninters;connind++){
			if(connind < this->ninters){
				weight=this->wordination[connind]->GetWeight();
			}
			
			if(antweight != weight || connind == this->ninters){
				if(nantw > 0){
					if(fprintf(fh,"%i %g\n",nantw,antweight) <= 0){
						throw EDLUTException(12,33,4,0);
					}
				}
				
				antweight=weight;
				nantw=1;
			}else{
				nantw++;
			}
		}
		
		// fprintf(fh,"// end of written data\n");
		
		fclose(fh);
	}else{
		throw EDLUTException(12,30,27,0);
	}
	
}

ostream & Network::PrintInfo(ostream & out) {
	int ind;

	out << "- Neuron types:" << endl;

	for(ind=0;ind<this->nneutypes;ind++){
		out << "\tType: " << ind << endl;

		this->neutypes[ind][0]->PrintInfo(out);
	}
   
	out << "- Neurons:" << endl;
   	
   	for(ind=0;ind<this->nneurons;ind++){
		this->neurons[ind].PrintInfo(out);
	}

   	out << "- Weight change types:" << endl;

	for(ind=0;ind<this->nwchanges;ind++){
		out << "\tChange: " << ind << endl;
		this->wchanges[ind]->PrintInfo(out);
	}

	out << "- Interconnections:" << endl;

	for(ind=0; ind<this->ninters; ind++){
		out << "\tConnection: " << ind << endl;
		this->inters[ind].PrintInfo(out);
	}
	
	return out;
}

int Network::GetNumberOfQueues(){
	return NumberOfQueues;
}


double Network::GetMinInterpropagationTime(){
	double time = 10000000;
	for(int i=0; i<this->ninters; i++){
		if(inters[i].GetSource()->get_OpenMP_queue_index()!=inters[i].GetTarget()->get_OpenMP_queue_index() && inters[i].GetDelay()<time){
			time=inters[i].GetDelay();
		}
	}

	if(time == 10000000){
		time=0;
	}
	return time;
}

double Network::RoundPropagationDelay(double time){
	double result=floor(time*invminpropagationdelay + minpropagationdelay*0.5)*minpropagationdelay;
	if(result<=0){
		return minpropagationdelay;
	} 
	return result;
}