/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			OpenCLRegularMemoryImportImageContainer.txx
	Version:		
	Date:			

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
	See RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/


#include "OpenCLImageMemoryImportImageContainer.h"
#include "OpenCLRegularMemoryImportImageContainer.h"


namespace ritk
{



//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>
::OpenCLImageMemoryImportImageContainer(){

	m_numAllocations = 0;
	m_size_width = 0;
	m_size_height = 0;
	m_OpenCLMemory = NULL;

	
}


//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>
::~OpenCLImageMemoryImportImageContainer(){

	while(m_numAllocations > 0)
		DeallocateManagedMemory();
	DeallocateOpenCLMemory();

}

//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
AllocateManually(ElementIdentifier size_width, ElementIdentifier size_height){

	m_size_width = size_width;
	m_size_height = size_height;

	if(m_OpenCLMemory != NULL){
		OpenCLContext::GetInstance().oclReleaseMemObject(m_OpenCLMemory);
		m_OpenCLMemory = NULL;
	}

	cl_image_format imgFormat = getOclImgFormat();
	
	m_OpenCLMemory = OpenCLContext::GetInstance().oclCreateImage2D(CL_MEM_READ_WRITE, &imgFormat, m_size_width, m_size_height, 0, NULL);

	
}


//-----------------------------------------------------------------------------
template<>
cl_image_format
OpenCLImageMemoryImportImageContainer<unsigned long, float>::
getOclImgFormat(){
	
	cl_image_format imgFormat;

	imgFormat.image_channel_order = CL_R;

	imgFormat.image_channel_data_type = CL_FLOAT;

	return imgFormat;
}

//-----------------------------------------------------------------------------
template<>
cl_image_format
OpenCLImageMemoryImportImageContainer<unsigned long, unsigned char>::
getOclImgFormat(){
	
	cl_image_format imgFormat;

	imgFormat.image_channel_order = CL_R;

	imgFormat.image_channel_data_type = CL_UNSIGNED_INT8;
	
	return imgFormat;
}

//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
cl_image_format
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
getOclImgFormat(){
	
	clDie("Template Format not implemented!");

}
//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
DeallocateOpenCLMemory(){
	

	if(m_OpenCLMemory != NULL){
			if(m_numAllocations > 0) clDie("OpenCLMemory still in use!");
			OpenCLContext::GetInstance().oclReleaseMemObject(m_OpenCLMemory);
			m_OpenCLMemory = NULL;
	}

}



//-----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
PrintSelf(std::ostream& os, itk::Indent indent) const
{
	os << "OpenCLImageMemoryImportImageContainer" << std::endl;
	Superclass::PrintSelf(os,indent);
}


//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
typename OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::Element*
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
AllocateElements(ElementIdentifier size) const
{
	std::cout << "OpenCLImageMemory::Allocate(" << size << ")" << std::endl;

	if(size != m_size_width * m_size_height)
		clDie("size != x*y");

	m_numAllocations++;

	// Call the superclass' implementation to allocate host memory
	return Superclass::AllocateElements(size);
}

//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
DeallocateManagedMemory()
{

	std::cout << "OpenCLImageMemory::Deallocate()" << std::endl;

	if(m_numAllocations <= 0)
		clDie("nothing to deallocate! (" << m_numAllocations << ")");

	m_numAllocations--;

	// Call the superclass' implementation to deallocate host memory
	Superclass::DeallocateManagedMemory();
}


//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
SynchronizeDevice()
{
	//TODO
	if(this->GetDeviceMTime() < this->GetHostMTime()){
	
		//std::cout << "synchronizing to device..." << std::endl;

		size_t origin [3] = {0,0,0};
		size_t region [3] = {m_size_width, m_size_height, 1};
		

		OpenCLContext::GetInstance().oclEnqueueWriteImage(m_OpenCLMemory, CL_FALSE, origin, region, 0, 0, GetBufferPointer(), 0, NULL, NULL);

	}
	// Synchronize MTimes
	Superclass::SynchronizeDevice();
}

//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
SynchronizeHost()
{
	//TODO
	if(this->GetHostMTime() < this->GetDeviceMTime()){
	
		//std::cout << "synchronizing to host..." << std::endl;

		size_t origin [3] = {0,0,0};
		size_t region [3] = {m_size_width, m_size_height, 1};
			
		OpenCLContext::GetInstance().oclEnqueueReadImage(m_OpenCLMemory, CL_TRUE, origin, region, 0, 0, GetBufferPointer(), 0, NULL, NULL);

		//std::cout << "synchronizing done. calling superclass synchronize to host..." << std::endl;
	
	}
	// Synchronize MTimes
	Superclass::SynchronizeHost();
}

//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
cl_mem OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
GetOpenCLMemoryPointer()
{
	DeviceModified();
	return m_OpenCLMemory;
}


//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
const cl_mem OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
GetOpenCLMemoryPointer() const
{
	return m_OpenCLMemory;
}


//----------------------------------------------------------------------------
template<typename TElementIdentifier, typename TElement>
void 
OpenCLImageMemoryImportImageContainer<TElementIdentifier, TElement>::
DeepCopy(const GenericImportImageContainer *Container)
{
	// Call the superclass implementation first. This will ensure correct memory allocation
	Superclass::DeepCopy(Container);

	// Do we have a valid OpenCL container?
	const OpenCLImportImageContainer *OpenCLContainer = dynamic_cast<const OpenCLImportImageContainer*>(Container);
	if ( !OpenCLContainer )
		return;
		
	// if host memory newer than device memory, skip that step as host memory has already been copied
	if ( OpenCLContainer->GetHostMTime() > OpenCLContainer->GetDeviceMTime() )
		return;

	// prepare Copy by casting to non const
	OpenCLImportImageContainer *NonConstContainer = const_cast<OpenCLImportImageContainer*>(OpenCLContainer);
	if ( !NonConstContainer )
		itkExceptionMacro(<<"Could not const cast input container...");
	
	
	// check for input container type
	Self * SelfContainer = dynamic_cast<Self *>(NonConstContainer);
	OpenCLRegularMemoryImportImageContainer<TElementIdentifier, TElement> * RegularMemoryContainer = dynamic_cast<OpenCLRegularMemoryImportImageContainer<TElementIdentifier, TElement> *>(NonConstContainer);
	
	
	// copy depending on input container type
	if(SelfContainer != NULL)
	{

		size_t origin [3] = {0,0,0};
		size_t region [3] = {m_size_width, m_size_height, 1};

		OpenCLContext::GetInstance().oclEnqueueCopyImage(SelfContainer->GetOpenCLMemoryPointer(), m_OpenCLMemory, origin, origin, region, 0, NULL, NULL);

		m_DeviceMTime = SelfContainer->m_DeviceMTime;

	}
	else if (RegularMemoryContainer != NULL)
	{
		
		size_t origin [3] = {0,0,0};
		size_t region [3] = {m_size_width, m_size_height, 1};

		OpenCLContext::GetInstance().oclEnqueueCopyBufferToImage(RegularMemoryContainer->GetOpenCLMemoryPointer(), m_OpenCLMemory, 0, origin, region, 0, NULL, NULL);
		
		DeviceModified();		

	}
	else
		clDie("No valid OpenCLType found!");
	



}





} // namespace ritk