#include "Network.h"

#include <iostream>

#include "Basics.h"
#include "Cells.h"
#include "Synapses.h"


#define DEBUG_INPUT 0
#define DEBUG_RUN 0

using namespace std;

void Connection::ReadFromFile(ifstream* fList) {
	int TargetID;	
	*fList >> TargetID;
	*fList >> Delay;
	*fList >> Weight;

	Target = Network::SynapseList[TargetID];

	#if DEBUG_INPUT > 4
	cout << "              Reading connection from file: target " << TargetID << " ("<< Target<<"), delay " << Delay << ", w " << Weight << endl;
	#endif
	

}

vector<Population> Network::Populations;
vector<Connectivity> Network::Connectivities;

vector<Neuron*> Network::NeuronList;
vector<Synapse*> Network::SynapseList;

int* Network::nConnectionPerCell = NULL;
Connection** Network::ConnectionList = NULL;

queue<SpikeEvent> Network::SpikeQueue;

ofstream* Network::fOutVm = NULL;
ofstream* Network::fOutSpikes = NULL;
ofstream* Network::fOutSynState = NULL;

int Network::AddNeuron(Neuron* n) {
	int id = NeuronList.size();
	NeuronList.push_back(n);
	return id;
}

int Network::AddSynapse(Synapse* s) {
	int id = SynapseList.size();
	SynapseList.push_back(s);
	#if DEBUG_INPUT > 3
	cout << "    Synapse added: " << s << endl;
	#endif
	return id;
}

void Network::ReadCells(ifstream* fCell) {
	// Make outline of the neurons:
	#if DEBUG_INPUT > 0
	cout << "Reading cell outline" << endl;
	#endif
	int nPopulation;
	*fCell >> nPopulation;
	for(int iPopulation=0;iPopulation<nPopulation;iPopulation++) {
		int t, n;
		*fCell >> t;
		*fCell >> n;
		Populations.push_back(Population(t,n));
		#if DEBUG_INPUT > 1
		cout << "Population " << iPopulation << " contains " << n << " cells of type " << t << endl;
		#endif
	}

	#if DEBUG_INPUT > 0
	cout << "Making cells" << endl;
	#endif
	// Make cells
	for(int iPopulation=0;iPopulation<nPopulation;iPopulation++) {
		int nNew = Populations[iPopulation].nNeuron;
		Populations[iPopulation].NeuronList = new Neuron*[nNew];
		// Make neurons one-by-one and assign to list:
		switch(Populations[iPopulation].TypeID) {
			case TYPE_LIF:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypeLIF;
				break;
			case TYPE_IZHIKEVICH:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypeIzhikevich;
				//Populations[iPopulation].NeuronList = new NeuronTypeIzhikevich[nNew];
				break;
			case TYPE_POISSON:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypePoisson;
				//Populations[iPopulation].NeuronList = new NeuronTypePoisson[nNew];
				break;
		    case TYPE_HH:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypeHH;
				break;
		    case TYPE_TEST:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypeTest;
				break;
			case TYPE_POISSONSTEP:
				for(int iNew=0; iNew<nNew; iNew++) Populations[iPopulation].NeuronList[iNew] = new NeuronTypePoissonStep;
				//Populations[iPopulation].NeuronList = new NeuronTypePoisson[nNew];
				break;
            cout << "No neuron Case found" << endl;                	
		}
	}
		
	#if DEBUG_INPUT > 0
	cout << "Reading param/state" << endl;
	#endif
	int nNeuron = NeuronList.size();
	for(int iNeuron=0; iNeuron<nNeuron; iNeuron++) {
		#if DEBUG_INPUT > 2
		cout << "  Processing cell " << iNeuron << endl;
		#endif
		NeuronList[iNeuron]->ReadParamState(fCell);
	}
}

void Network::ReadSynapses(ifstream* fSyn) {
	// Make outline of connectivies:
	int nConnectivity;
	#if DEBUG_INPUT > 0
	cout << "Reading synapse outline" << endl;
	#endif
	*fSyn >> nConnectivity;
	for(int iConnectivity=0; iConnectivity<nConnectivity; iConnectivity++) {
		int t, p;
		*fSyn >> t;
		*fSyn >> p;
		Connectivities.push_back(Connectivity(t,p));
		#if DEBUG_INPUT > 1
		cout << "Connectivity " << iConnectivity << " of type " << t << " to population " << p << endl;
		#endif
	}

/*
	// Make all the synapses:
	#if DEBUG_INPUT > 0
	cout << "Making synapses" << endl;
	#endif
	for(int iConnectivity=0; iConnectivity<nConnectivity; iConnectivity++) {
		int nNew = Populations[Connectivities[iConnectivity].TargetPopulation].nNeuron; // Number of neurons in target population
		Synapse* NewSynapse;
		switch(Connectivities[iConnectivity].TypeID) {
			case TYPE_DELTA:
				NewSynapse = new SynapseTypeDelta[nNew];
				break;
			case TYPE_EXPONENTIAL:
				NewSynapse = new SynapseTypeExponential[nNew];
				break;
		}
		

		// Add the new synapses to their host neurons
		#if DEBUG_INPUT > 1
		cout << "  Synapses made for connectivity " << iConnectivity << ". Linking to cells" << endl;
		#endif
		Neuron* HostCell = Populations[Connectivities[iConnectivity].TargetPopulation].NeuronList;
		for(int iCell=0; iCell<nNew; iCell++) {
			#if DEBUG_INPUT > 2
			cout << "    Linking to cell " << iCell << " of population " << Connectivities[iConnectivity].TargetPopulation << endl;
			#endif
			HostCell[iCell].AddSynapse(&NewSynapse[iCell]);
		}
	}
*/
	// Make all the synapses:
	#if DEBUG_INPUT > 0
	cout << "Making synapses" << endl;
	#endif

	for(int iConnectivity=0; iConnectivity<nConnectivity; iConnectivity++) {
		int nNeuron = Populations[Connectivities[iConnectivity].TargetPopulation].nNeuron; // Number of neurons in target population
        Neuron** PopulationCellList = Populations[Connectivities[iConnectivity].TargetPopulation].NeuronList;

		for(int iNeuron=0; iNeuron<nNeuron; iNeuron++) {
			switch(Connectivities[iConnectivity].TypeID) {
				case TYPE_DELTA:
					PopulationCellList[iNeuron]->AddSynapse(new SynapseTypeDelta);
					break;
				case TYPE_EXPONENTIAL:
					PopulationCellList[iNeuron]->AddSynapse(new SynapseTypeExponential);
					//	HostCell[iNeuron].AddSynapse(new SynapseTypeExponential);
					break;
				case TYPE_ALPHALILEY:
					PopulationCellList[iNeuron]->AddSynapse(new SynapseTypeAlphaLiley);
					break;					
		     cout << "Synapse type not found";
			}
		}
	}
	
	#if DEBUG_INPUT > 0
	cout << "Reading param/state of synapses" << endl;
	#endif
	int nSynapse = SynapseList.size();
	for(int iSynapse=0; iSynapse<nSynapse; iSynapse++) {
		#if DEBUG_INPUT > 2
		cout << "  Reading synapse " << iSynapse << endl;
		#endif
		#if DEBUG_INPUT > 4
		cout << "  Address: " << SynapseList[iSynapse] << endl;
		#endif
		SynapseList[iSynapse]->ReadParamState(fSyn);
	}
}

void Network::ReadConnections(ifstream* fCon) {
	int nNeuron = NeuronList.size();
	//	cout << "nNeuron = " << nNeuron;
	nConnectionPerCell = new int[nNeuron];
	ConnectionList = new Connection*[nNeuron];
	
	#if DEBUG_INPUT > 0
	cout << "Processing connections" << endl;
	#endif
	for(int iNeuron=0; iNeuron<nNeuron; iNeuron++) {
		int nConnect;
		*fCon >> nConnect;
		nConnectionPerCell[iNeuron] = nConnect;
		#if DEBUG_INPUT > 1
		cout << "  Neuron " << iNeuron << " makes " << nConnect << " connections" << endl;
		#endif
		if(nConnect > 0) {
			ConnectionList[iNeuron] = new Connection[nConnect];
			for(int iConnect=0; iConnect<nConnect; iConnect++) {
				#if DEBUG_INPUT > 2
				cout << "    processing connection " << iConnect << endl;
				#endif
				ConnectionList[iNeuron][iConnect].ReadFromFile(fCon);
			}
		} else {
			ConnectionList[iNeuron] = NULL;
		}
	}
}
		
void Network::AddSpike(Gid id) {
	SpikeQueue.push(SpikeEvent(DynSys::Time,id));
}

void Network::DistributeSpike() {
	while(!SpikeQueue.empty()) {
		//#if DEBUG_RUN > 0
		//cout << "Distributing spikes" << endl;
		//#endif
		int CellId = SpikeQueue.front().Source.LocalID;
		double SpikeTime = SpikeQueue.front().Time;		

		// Write this spike to file, if necessary
		if(fOutSpikes != NULL) *fOutSpikes << SpikeTime << "\t" << CellId << "\n";

		// Distribute spike to all its connections:
		int nConnect = nConnectionPerCell[CellId];
		#if DEBUG_RUN > 1
		cout << "  Distributing spike from neuron " << CellId << " at time " << SpikeTime << " to " << nConnect << " connections" << endl;
		#endif

		for(int iConnect=0; iConnect<nConnect; iConnect++) {
			Synapse* Target = ConnectionList[CellId][iConnect].Target;
			double ActivationTime = SpikeTime + ConnectionList[CellId][iConnect].Delay;
			#if DEBUG_RUN > 2
			cout << "Adding event to synapse " << Target << " for time " << ActivationTime << endl;
			#endif
			Target->AddSpike(ActivationTime,ConnectionList[CellId][iConnect].Weight);
		}

		SpikeQueue.pop();
	}
}

void Network::PrintAll() {
	if(fOutVm != NULL) {
		for(unsigned int iNeuron=0; iNeuron<NeuronList.size(); iNeuron++) {
			*fOutVm << NeuronList[iNeuron]->GetVm() << "\t";
		}
		*fOutVm << "\n";
	}
		if(fOutSynState != NULL) {
		for(unsigned int iSyn=0; iSyn<SynapseList.size(); iSyn++) {
			*fOutSynState << SynapseList[iSyn]->GetSynState() << "\t";
		}
		*fOutSynState << "\n";
	}
}