/***************************************************************************
 *                           SRMTableBasedModel.cpp                        *
 *                           -------------------                           *
 * copyright            : (C) 2010 by Jesus Garrido                        *
 * 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/neuron_model/SRMTableBasedModel.h"
#include "../../include/neuron_model/NeuronModelTable.h"
#include "../../include/neuron_model/VectorSRMState.h"

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

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


void SRMTableBasedModel::LoadNeuronModel(string ConfigFile) throw (EDLUTFileException){
	FILE *fh;
	long Currentline = 0L;
	fh=fopen(ConfigFile.c_str(),"rt");
	if(fh){
		Currentline=1L;
		skip_comments(fh,Currentline);
		if(fscanf(fh,"%i",&this->NumStateVar)==1){
			unsigned int nv;

			// Initialize all vectors.
			this->StateVarTable = (NeuronModelTable **) new NeuronModelTable * [this->NumStateVar];
			this->StateVarOrder = (unsigned int *) new unsigned int [this->NumStateVar];

			// Auxiliary table index
			unsigned int * TablesIndex = (unsigned int *) new unsigned int [this->NumStateVar];

			skip_comments(fh,Currentline);
			for(nv=0;nv<this->NumStateVar;nv++){
				if(fscanf(fh,"%i",&TablesIndex[nv])!=1){
					throw EDLUTFileException(13,41,3,1,Currentline);
				}
			}

			skip_comments(fh,Currentline);

			float InitValue;
			InitValues=new float[NumStateVar+3]();

			// Create a new initial state
			this->InitialState = (VectorSRMState *) new VectorSRMState(this->NumStateVar+3,0, false);
//			this->InitialState->SetLastUpdateTime(0);
//			this->InitialState->SetNextPredictedSpikeTime(NO_SPIKE_PREDICTED);
//			this->InitialState->SetStateVariableAt(0,0);

			for(nv=0;nv<this->NumStateVar;nv++){
				if(fscanf(fh,"%f",&InitValue)!=1){
					throw EDLUTFileException(13,42,3,1,Currentline);
				} else {
					InitValues[nv+1]=InitValue;
//					this->InitialState->SetStateVariableAt(nv+1,InitValue);
				}
			}


			skip_comments(fh,Currentline);
			unsigned int FiringIndex;
			if(fscanf(fh,"%i",&FiringIndex)==1){
				skip_comments(fh,Currentline);
				if(fscanf(fh,"%i",&this->NumSynapticVar)==1){
					skip_comments(fh,Currentline);

					this->SynapticVar = (unsigned int *) new unsigned int [this->NumSynapticVar];
					for(nv=0;nv<this->NumSynapticVar;nv++){
						if(fscanf(fh,"%i",&this->SynapticVar[nv])!=1){
							throw EDLUTFileException(13,40,3,1,Currentline);
						}
					}


					skip_comments(fh,Currentline);
					if(fscanf(fh,"%i",&this->LastSpikeVar)!=1){
						throw EDLUTFileException(13,46,3,1,Currentline);
					}

					skip_comments(fh,Currentline);
					if(fscanf(fh,"%i",&this->SeedVar)!=1){
						throw EDLUTFileException(13,47,3,1,Currentline);
					}

					skip_comments(fh,Currentline);
					if(fscanf(fh,"%i",&this->NumTables)==1){
						unsigned int nt;
						int tdeptables[MAXSTATEVARS];
						int tstatevarpos,ntstatevarpos;

						this->Tables = (NeuronModelTable *) new NeuronModelTable [this->NumTables];

						// Update table links
						for(nv=0;nv<this->NumStateVar;nv++){
							this->StateVarTable[nv] = this->Tables+TablesIndex[nv];
						}
						this->FiringTable = this->Tables+FiringIndex;

						for(nt=0;nt<this->NumTables;nt++){
							this->Tables[nt].LoadTableDescription(fh, Currentline);
						}

						this->NumTimeDependentStateVar = 0;
						for(nt=0;nt<this->NumStateVar;nt++){
							for(nv=0;nv<this->StateVarTable[nt]->GetDimensionNumber() && this->StateVarTable[nt]->GetDimensionAt(nv)->statevar != 0;nv++);
							if(nv<this->StateVarTable[nt]->GetDimensionNumber()){
								tdeptables[nt]=1;
								this->NumTimeDependentStateVar++;
							}else{
								tdeptables[nt]=0;
							}
						}

						tstatevarpos=0;
						ntstatevarpos=this->NumTimeDependentStateVar; // we place non-t-depentent variables in the end, so that they are evaluated afterwards
						for(nt=0;nt<this->NumStateVar;nt++){
							this->StateVarOrder[(tdeptables[nt])?tstatevarpos++:ntstatevarpos++]=nt;
						}
					}else{
						throw EDLUTFileException(13,37,3,1,Currentline);
					}
				}else{
					throw EDLUTFileException(13,36,3,1,Currentline);
				}
			}else{
				throw EDLUTFileException(13,35,3,1,Currentline);
			}
		}else{
			throw EDLUTFileException(13,34,3,1,Currentline);
		}
	}else{
		throw EDLUTFileException(13,25,13,0,Currentline);
	}
}

void SRMTableBasedModel::LoadNeuronModel() throw (EDLUTFileException){

	this->LoadNeuronModel(this->GetModelID()+".cfg");

	this->LoadTables(this->GetModelID()+".dat");

}

void SRMTableBasedModel::UpdateState(int index, VectorNeuronState * State, double CurrentTime){
	double AuxTime = State->GetLastUpdateTime(index);

	VectorSRMState * StateAux = (VectorSRMState *) State;

	StateAux->AddElapsedTime(index, CurrentTime-AuxTime);

	TableBasedModel::UpdateState(index, State, CurrentTime);
}

void SRMTableBasedModel::SynapsisEffect(int index, Interconnection * InputConnection){
	this->GetVectorNeuronState()->IncrementStateVariableAtCPU(index,this->SynapticVar[InputConnection->GetType()]+1,InputConnection->GetWeight()*exp(1.0));
}

double SRMTableBasedModel::NextFiringPrediction(int index, VectorNeuronState * State){
	State->SetStateVariableAt(index,this->LastSpikeVar+1,((VectorSRMState *) State)->GetLastSpikeTime(index));
	State->SetStateVariableAt(index,this->SeedVar+1,rand()%10);
	return this->FiringTable->TableAccess(index,State);
}

double SRMTableBasedModel::EndRefractoryPeriod(int index, VectorNeuronState * State){
	return 0.0;
}

SRMTableBasedModel::SRMTableBasedModel(string NeuronTypeID, string NeuronModelID): TableBasedModel(NeuronTypeID, NeuronModelID){

}

SRMTableBasedModel::~SRMTableBasedModel(){

}

VectorNeuronState * SRMTableBasedModel::InitializeState(){
	//return (VectorSRMState *) new VectorSRMState(*((VectorSRMState *) this->InitialState));
	return InitialState;
}

InternalSpike * SRMTableBasedModel::GenerateNextSpike(InternalSpike *  OutputSpike){

	Neuron * SourceCell = OutputSpike->GetSource();

	int SourceIndex=SourceCell->GetIndex_VectorNeuronState();

	VectorNeuronState * CurrentState = SourceCell->GetVectorNeuronState();

	InternalSpike * NextSpike = 0;

	this->UpdateState(SourceIndex,(VectorSRMState *)CurrentState,OutputSpike->GetTime());

	double PredictedSpike = this->NextFiringPrediction(SourceIndex,(VectorSRMState *)CurrentState);

	if (PredictedSpike!=NO_SPIKE_PREDICTED){
		PredictedSpike += CurrentState->GetLastUpdateTime(SourceIndex);

		NextSpike = new InternalSpike(PredictedSpike,SourceCell);
	}

	SourceCell->GetVectorNeuronState()->SetNextPredictedSpikeTime(SourceIndex,PredictedSpike);

	return NextSpike;
}

void SRMTableBasedModel::InitializeStates(int N_neurons, int OpenMPQueueIndex){
	InitialState->InitializeStates(N_neurons, InitValues);
}