/***************************************************************************
 *                           AdExTimeDrivenModel.cpp                       *
 *                           -------------------                           *
 * copyright            : (C) 2015 by Francisco Naveros                    *
 * email                : fnaveros@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/neuron_model/AdExTimeDrivenModel.h"
#include "../../include/neuron_model/VectorNeuronState.h"

#include <iostream>
#include <cmath>
#include <string>

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

#include "../../include/spike/EDLUTFileException.h"
#include "../../include/spike/Neuron.h"
#include "../../include/spike/InternalSpike.h"
#include "../../include/spike/PropagatedSpike.h"
#include "../../include/spike/Interconnection.h"

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

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


void AdExTimeDrivenModel::LoadNeuronModel(string ConfigFile) throw (EDLUTFileException){
	FILE *fh;
	long Currentline = 0L;
	fh=fopen(ConfigFile.c_str(),"rt");
	if(fh){
		this->State = (VectorNeuronState *) new VectorNeuronState(N_NeuronStateVariables, true);

		//INTEGRATION METHOD
		this->integrationMethod = LoadIntegrationMethod::loadIntegrationMethod((TimeDrivenNeuronModel *)this, fh, &Currentline, N_NeuronStateVariables, N_DifferentialNeuronState, N_TimeDependentNeuronState);

		this->integrationMethod->SetBiFixedStepParameters(VT,VT,0.2f);

		this->integrationMethod->Calculate_conductance_exp_values();
	}
	fclose(fh);
}

void AdExTimeDrivenModel::SynapsisEffect(int index, Interconnection * InputConnection){
//	this->GetVectorNeuronState()->IncrementStateVariableAtCPU(index,N_DifferentialNeuronState+InputConnection->GetType(),InputConnection->GetWeight());

	float new_conductance=this->GetVectorNeuronState()->GetStateVariableAt(index,N_DifferentialNeuronState+InputConnection->GetType())+InputConnection->GetWeight();
	if(new_conductance>max_exc){
		new_conductance=max_exc;
	}
	this->GetVectorNeuronState()->SetStateVariableAt(index,N_DifferentialNeuronState+InputConnection->GetType(),new_conductance);
}

//this neuron model is implemented in a milisecond scale.
AdExTimeDrivenModel::AdExTimeDrivenModel(string NeuronTypeID, string NeuronModelID): TimeDrivenNeuronModel(NeuronTypeID, NeuronModelID, MilisecondScale){
		a=1.0f;  //nS
		b=9.0f;  //pA
		TSF=2.0f;  //mV
		inv_TSF=1.0f/TSF;
		VT=-50.0f;//mV
		tauw=50.0f ; //ms   
		inv_tauw=1.0f/tauw;
		eexc=0.0f;//mV
		einh=-80.0f;//mV
		Ereset=-80.0f;//mV
		Eleak=-65.0f;//mV
		gleak=10;//nS
		cm=110.0f;//pF
		inv_cm=1.0f/cm;
		texc=5.0f;//ms
		inv_texc=1.0f/texc;
		tinh=10.0f;//ms
		inv_tinh=1.0f/tinh;
		max_exc=30;//nS
}

AdExTimeDrivenModel::~AdExTimeDrivenModel(void)
{
}

void AdExTimeDrivenModel::LoadNeuronModel() throw (EDLUTFileException){
	this->LoadNeuronModel(this->GetModelID()+".cfg");
}

VectorNeuronState * AdExTimeDrivenModel::InitializeState(){
	return this->GetVectorNeuronState();
}


InternalSpike * AdExTimeDrivenModel::ProcessInputSpike(Interconnection * inter, Neuron * target, double time){
	// Add the effect of the input spike
	this->SynapsisEffect(target->GetIndex_VectorNeuronState(),inter);

	return 0;
}






bool AdExTimeDrivenModel::UpdateState(int index, double CurrentTime){

	
	bool * internalSpike=State->getInternalSpike();


	//float * NeuronState;
	//NeuronState[0] --> V 
	//NeuronState[1] --> w 
	//NeuronState[2] --> gexc 
	//NeuronState[3] --> ginh 


	for (int i=0; i< State->GetSizeState(); i++){

		float * NeuronState=State->GetStateVariableAt(i);
	
		internalSpike[i]=this->integrationMethod->NextDifferentialEcuationValues(i, NeuronState);

		State->SetLastUpdateTime(i,CurrentTime);
	}

	this->CheckValidIntegration();

	return false;
}




ostream & AdExTimeDrivenModel::PrintInfo(ostream & out){
	return out;
}	



void AdExTimeDrivenModel::InitializeStates(int N_neurons, int OpenMPQueueIndex){
	//Initialize neural state variables.
	float initialization[] = {this->Eleak,0.0f,0.0f,0.0f};
	State->InitializeStates(N_neurons, initialization);

	//Initialize integration method state variables.
	this->integrationMethod->InitializeStates(N_neurons, initialization);
}

bool AdExTimeDrivenModel::EvaluateSpikeCondition(float previous_V, float * NeuronState, int index, float elpasedTimeInNeuronModelScale){
	if (NeuronState[0] > 0.0f){
		NeuronState[0] = this->Ereset;//V
		NeuronState[1] += this->b;//w 
		State->NewFiredSpike(index);
		this->integrationMethod->resetState(index);
		return true;
	}
	return false;
}

void AdExTimeDrivenModel::EvaluateDifferentialEcuation(float * NeuronState, float * AuxNeuronState, int index, float elapsed_time){
	float V=NeuronState[0];
	float w=NeuronState[1];
	float gexc=NeuronState[2];
	float ginh=NeuronState[3];

	//V
	if(V<=-20.0f){
		//V
		AuxNeuronState[0]=(-gleak*(V-Eleak) + gleak*TSF*ExponentialTable::GetResult((V-VT)*inv_TSF) - w - gexc * (V - this->eexc) - ginh * (V - this->einh))*this->inv_cm;
		//w
		AuxNeuronState[1]=(a*(V - Eleak) - w)*this->inv_tauw;
	}else if(V<=0.0f){
		float Vm=-20.0f;
		//V
		AuxNeuronState[0]=(-gleak*(Vm-Eleak) + gleak*TSF*ExponentialTable::GetResult((Vm-VT)*inv_TSF) - w - gexc * (Vm - this->eexc) - ginh * (Vm - this->einh))*this->inv_cm;
		//w
		AuxNeuronState[1]=(a*(V - Eleak) - w)*this->inv_tauw;
	}else{
		//V
		AuxNeuronState[0]=0;
		//w
		AuxNeuronState[1]=0;
	}
}


void AdExTimeDrivenModel::EvaluateTimeDependentEcuation(float * NeuronState, float elapsed_time, int elapsed_time_index){
	float limit=1e-20;
	float * Conductance_values=this->Get_conductance_exponential_values(elapsed_time_index);
	
	if(NeuronState[N_DifferentialNeuronState]<limit){
		NeuronState[N_DifferentialNeuronState]=0.0f;
	}else{
		NeuronState[N_DifferentialNeuronState]*= Conductance_values[0];
	}
	if(NeuronState[N_DifferentialNeuronState+1]<limit){
		NeuronState[N_DifferentialNeuronState+1]=0.0f;
	}else{
		NeuronState[N_DifferentialNeuronState+1]*= Conductance_values[1];
	}	
}


void AdExTimeDrivenModel::Calculate_conductance_exp_values(int index, float elapsed_time){
	//excitatory synapse.
	Set_conductance_exp_values(index, 0, exp(-elapsed_time*this->inv_texc));
	//inhibitory synapse.
	Set_conductance_exp_values(index, 1, exp(-elapsed_time*this->inv_tinh));
}


int AdExTimeDrivenModel::CheckSynapseTypeNumber(int Type){
	if(Type<N_TimeDependentNeuronState && Type>=0){
		return Type;
	}else{
		cout<<"Neuron model "<<this->GetTypeID()<<", "<<this->GetModelID()<<" does not support input synapses of type "<<Type<<endl;
		return 0;
	}
}




