/***************************************************************************
 *                           VectorNeuronState_GPU.cpp                     *
 *                           -------------------                           *
 * copyright            : (C) 2012 by Jesus Garrido 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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "../../include/neuron_model/VectorNeuronState_GPU.h"
#include "../../include/cudaError.h"

#include <iostream>
//Library for CUDA
#include <helper_cuda.h>

using namespace std;

	VectorNeuronState_GPU::VectorNeuronState_GPU(unsigned int NumVariables):VectorNeuronState(NumVariables, true, true){
	};

	VectorNeuronState_GPU::~VectorNeuronState_GPU(){
		HANDLE_ERROR(cudaFree(VectorNeuronStates_GPU));
		HANDLE_ERROR(cudaFree(LastUpdateGPU));
		HANDLE_ERROR(cudaFree(LastSpikeTimeGPU));

		HANDLE_ERROR(cudaFreeHost(AuxStateCPU));
		HANDLE_ERROR(cudaFreeHost(InternalSpikeCPU));

		//GPU can use MapHostMemory
		if(!prop.canMapHostMemory){
			HANDLE_ERROR(cudaFree(AuxStateGPU));
			HANDLE_ERROR(cudaFree(InternalSpikeGPU));
		}
	}


void VectorNeuronState_GPU::InitializeStatesGPU(int N_Neurons, float * initialization, int N_AuxNeuronStates, cudaDeviceProp NewProp){
	prop=NewProp;

	//Initilize State in CPU
	SetSizeState(N_Neurons);
	
	VectorNeuronStates = new float[GetNumberOfVariables()*GetSizeState()]();
	LastUpdate=new double[GetSizeState()]();
	LastSpikeTime=new double[GetSizeState()]();
	
	if(!TimeDriven){
		PredictedSpike=new double[GetSizeState()]();
		PredictionEnd=new double[GetSizeState()]();
	}else{
		InternalSpike=new bool[GetSizeState()]();
	}
	
	//For the GPU, we store all the variables of the same type in adjacent memory positions
	//to perform coalescent access to data.
	for(int z=0; z<GetNumberOfVariables(); z++){
		for (int j=0; j<GetSizeState(); j++){ 
			VectorNeuronStates[z*GetSizeState() + j]=initialization[z];
		}
	}

	for(int z=0; z<GetSizeState(); z++){
		LastSpikeTime[z]=100.0;
	}
	

	
	//Memory in CPU uses as buffer.
	//cudaHostAlloc allocate CPU memory with specific properties.
	HANDLE_ERROR(cudaHostAlloc((void**)&AuxStateCPU, N_AuxNeuronStates*GetSizeState()*sizeof(float),cudaHostAllocMapped));
	memset(AuxStateCPU,0,N_AuxNeuronStates*GetSizeState()*sizeof(float));
	HANDLE_ERROR(cudaHostAlloc((void**)&InternalSpikeCPU, GetSizeState()*sizeof(bool),cudaHostAllocMapped));


	//GPU can use MapHostMemory
	if(prop.canMapHostMemory){
		HANDLE_ERROR ( cudaHostGetDevicePointer( (void**)&AuxStateGPU,AuxStateCPU, 0 ) );
		HANDLE_ERROR ( cudaHostGetDevicePointer( (void**)&InternalSpikeGPU,InternalSpikeCPU, 0 ) );
	}
	//GPU can not use MapHostMemory.
	else{
		HANDLE_ERROR(cudaMalloc((void**)&AuxStateGPU, N_AuxNeuronStates*GetSizeState()*sizeof(float)));
		HANDLE_ERROR(cudaMalloc((void**)&InternalSpikeGPU, GetSizeState()*sizeof(bool)));
	}
	HANDLE_ERROR(cudaMalloc((void**)&VectorNeuronStates_GPU, GetNumberOfVariables()*GetSizeState()*sizeof(float)));
	HANDLE_ERROR(cudaMalloc((void**)&LastUpdateGPU, GetSizeState()*sizeof(double)));
	HANDLE_ERROR(cudaMalloc((void**)&LastSpikeTimeGPU, GetSizeState()*sizeof(double)));

	
	//Copy initial state from CPU to GPU
	HANDLE_ERROR(cudaMemcpy(VectorNeuronStates_GPU,VectorNeuronStates,GetNumberOfVariables()*GetSizeState()*sizeof(float),cudaMemcpyHostToDevice));
	HANDLE_ERROR(cudaMemcpy(LastUpdateGPU,LastUpdate,GetSizeState()*sizeof(double),cudaMemcpyHostToDevice));
	HANDLE_ERROR(cudaMemcpy(LastSpikeTimeGPU,LastSpikeTime,GetSizeState()*sizeof(double),cudaMemcpyHostToDevice));


}


bool * VectorNeuronState_GPU::getInternalSpike(){
	return InternalSpikeCPU;
}