/***************************************************************************
 *                           SinBufferedWeightChange.cpp                   *
 *                           -------------------                           *
 * copyright            : (C) 2016 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/learning_rules/SinBufferedWeightChange.h"

#include "../../include/learning_rules/SinBufferedState.h"

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

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

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

SinBufferedWeightChange::SinBufferedWeightChange(int NewLearningRuleIndex):AdditiveKernelChange(NewLearningRuleIndex), exponent(0){
}

SinBufferedWeightChange::~SinBufferedWeightChange(){
}


void SinBufferedWeightChange::InitializeConnectionState(unsigned int NumberOfSynapses){
	this->State=(ConnectionState *) new SinBufferedState(NumberOfSynapses, this->exponent,this->maxpos);
}

int SinBufferedWeightChange::GetNumberOfVar() const{
	return this->exponent+2;
}

int SinBufferedWeightChange::GetExponent() const{
	return this->exponent;
}

void SinBufferedWeightChange::LoadLearningRule(FILE * fh, long & Currentline) throw (EDLUTFileException){
	AdditiveKernelChange::LoadLearningRule(fh,Currentline);

	if(!(fscanf(fh,"%i",&this->exponent)==1)){
		throw EDLUTFileException(4,28,23,1,Currentline);
	}
	
	//exponent must be multiple of 2.
	if(exponent%2 == 1){
		exponent=(exponent/2)*2;
		cerr << "Warning: exponent in SinBufferedAdditiveKernel must be multiple of 2. It has been rounded to "<< exponent << endl;
	}
}


//void SinBufferedWeightChange::ApplyPreSynapticSpike(Interconnection * Connection,double SpikeTime){
//	
//
//	if(Connection->GetTriggerConnection()==false){
//		int LearningRuleIndex = Connection->GetLearningRuleIndex_withoutPost();
//
//		// Second case: the weight change is linked to this connection
//		Connection->IncrementWeight(this->a1pre);
//
//		State->SetLastUpdateTime(LearningRuleIndex,SpikeTime);
//	}else{
//		Neuron * TargetNeuron=Connection->GetTarget();
//
//		int aux=(TargetNeuron->GetInputNumberWithoutPostSynapticLearning()+NumberOfOpenMPThreads-1)/NumberOfOpenMPThreads;
//		int start, end;
//		for(int j=0; j<NumberOfOpenMPThreads; j++){
//			start=j*aux;
//			end=start+aux;
//			if(end>TargetNeuron->GetInputNumberWithoutPostSynapticLearning()){
//				end=TargetNeuron->GetInputNumberWithoutPostSynapticLearning();
//			}
//
//			#ifdef _OPENMP 
//				#if	_OPENMP >= OPENMPVERSION30
//					#pragma omp task if(j<(NumberOfOpenMPThreads-1)) firstprivate(start,end) shared(TargetNeuron, Connection)
//				#endif
//			#endif
//			{
//				for(int i=start; i<end; ++i){
//					Interconnection * interi=TargetNeuron->GetInputConnectionWithoutPostSynapticLearningAt(i);
//
//					if(interi->GetTriggerConnection()==false){
//						// Apply sinaptic plasticity driven by teaching signal
//						int LearningRuleIndex = interi->GetLearningRuleIndex_withoutPost();
//
//						// Update the presynaptic activity
//						State->SetNewUpdateTime(LearningRuleIndex, SpikeTime, false);
//						// Update synaptic weight
//						interi->IncrementWeight(this->a2prepre*State->GetPresynapticActivity(LearningRuleIndex));
//					}
//				}
//			}
//		}
//		#ifdef _OPENMP 
//			#if	_OPENMP >= OPENMPVERSION30
//				#pragma omp taskwait
//			#endif
//		#endif
//	}
//}



void SinBufferedWeightChange::ApplyPreSynapticSpike(Interconnection * Connection, double SpikeTime){


	if (Connection->GetTriggerConnection() == false){
		int LearningRuleIndex = Connection->GetLearningRuleIndex_withoutPost();

		// Second case: the weight change is linked to this connection
		Connection->IncrementWeight(this->a1pre);

		State->SetLastUpdateTime(LearningRuleIndex, SpikeTime);

		// Add the presynaptic spike influence
		State->ApplyPresynapticSpike(LearningRuleIndex);
	}
	else{
		Neuron * TargetNeuron = Connection->GetTarget();

		const int minimum = 500;
		int N_tasks;
		int aux;
		if (TargetNeuron->GetInputNumberWithoutPostSynapticLearning() > minimum * NumberOfOpenMPThreads){
			N_tasks = NumberOfOpenMPThreads;
			aux = (TargetNeuron->GetInputNumberWithoutPostSynapticLearning() + NumberOfOpenMPThreads - 1) / NumberOfOpenMPThreads;
		}
		else{
			N_tasks = (TargetNeuron->GetInputNumberWithoutPostSynapticLearning() + minimum - 1) / minimum;
			aux = minimum;
		}

		int start, end;
		for (int j = 0; j<N_tasks; j++){
			start = j*aux;
			end = start + aux;
			if (end>TargetNeuron->GetInputNumberWithoutPostSynapticLearning()){
				end = TargetNeuron->GetInputNumberWithoutPostSynapticLearning();
			}
#ifdef _OPENMP 
#if	_OPENMP >= OPENMPVERSION30
#pragma omp task firstprivate(start,end) shared(TargetNeuron, Connection)
#endif
#endif
			{
				for (int i = start; i<end; ++i){
					//We implement this weight increment mechanisme in order to improve the cache friendly. Thus we do not need to red a large number of none consecutive synapses from memory.
					//This weight increments will be acumulated in the synaptic weights when a spike will be propagated for this synapses.
					int LearningRuleIndex = TargetNeuron->IndexInputLearningConnections[1][i];

					if (LearningRuleIndex >= 0){
						// Update the presynaptic activity
						State->SetNewUpdateTime(LearningRuleIndex, SpikeTime, false);

						TargetNeuron->WeightIncrements[1][i] += this->a2prepre*State->GetPresynapticActivity(LearningRuleIndex);
					}
				}
			}
		}
#ifdef _OPENMP 
#if	_OPENMP >= OPENMPVERSION30
#pragma omp taskwait
#endif
#endif
	}
}