#include "CmGpuParamBlock.h"
#include "CmGpuParamDesc.h"
#include "CmHardwareBufferManager.h"
#include "CmException.h"

namespace CamelotEngine
{
	GpuParamBlockBuffer::GpuParamBlockBuffer(UINT32 size, GpuParamBlockUsage usage)
		:mData(CM_NEW_BYTES(size, ScratchAlloc)), mSize(size), mUsage(usage)
	{
		memset(mData, 0, mSize);
	}

	GpuParamBlockBuffer::~GpuParamBlockBuffer()
	{
		if(mData != nullptr)
			CM_DELETE_BYTES(mData, ScratchAlloc);
	}

	void GpuParamBlockBuffer::writeAll(const void* data)
	{
		memcpy(mData, data, mSize);
	}

	const UINT8* GpuParamBlockBuffer::getDataPtr(UINT32 offset) const
	{
#if CM_DEBUG_MODE
		if(offset < 0 || offset >= mSize)
		{
			CM_EXCEPT(InvalidParametersException, "Wanted range is out of buffer bounds. " \
				"Available range: 0 .. " + toString(mSize) + ". " \
				"Wanted range: " + toString(offset) + " .. " + toString(offset) + ".");
		}
#endif

		return &mData[offset];
	}

	GpuParamBlock::GpuParamBlock()
		:mDirty(true), mUsage(GPBU_DYNAMIC), mData(nullptr), mBuffer(nullptr)
	{
	}

	GpuParamBlock::~GpuParamBlock()
	{
		if(mData != nullptr)
			CM_DELETE_BYTES(mData, ScratchAlloc);
	}

	void GpuParamBlock::initialize(const GpuParamBlockDesc& desc, GpuParamBlockUsage usage)
	{
		mSize = desc.blockSize * sizeof(UINT32);
		mData = CM_NEW_BYTES(mSize, ScratchAlloc);
		memset(mData, 0, mSize);

		mUsage = usage;

		CoreObject::initialize();
	}

	void GpuParamBlock::initialize_internal()
	{
		mBuffer = createBuffer();

		CoreObject::initialize_internal();
	}

	void GpuParamBlock::destroy_internal()
	{
		if(mBuffer != nullptr)
			CM_DELETE(mBuffer, GpuParamBlockBuffer, PoolAlloc);

		CoreObject::destroy_internal();
	}

	GpuParamBlockBuffer* GpuParamBlock::createBuffer() const
	{
		return CM_NEW(GpuParamBlockBuffer, PoolAlloc) GpuParamBlockBuffer(mSize, mUsage);
	}

	void GpuParamBlock::write(UINT32 offset, const void* data, UINT32 size)
	{
#if CM_DEBUG_MODE
		if(offset < 0 || (offset + size) > mSize)
		{
			CM_EXCEPT(InvalidParametersException, "Wanted range is out of buffer bounds. " \
				"Available range: 0 .. " + toString(mSize) + ". " \
				"Wanted range: " + toString(offset) + " .. " + toString(offset + size) + ".");
		}
#endif

		memcpy(mData + offset, data, size);

		mDirty = true;
	}

	void GpuParamBlock::zeroOut(UINT32 offset, UINT32 size)
	{
#if CM_DEBUG_MODE
		if(offset < 0 || (offset + size) > mSize)
		{
			CM_EXCEPT(InvalidParametersException, "Wanted range is out of buffer bounds. " \
				"Available range: 0 .. " + toString(mSize) + ". " \
				"Wanted range: " + toString(offset) + " .. " + toString(offset + size) + ".");
		}
#endif

		memset(mData + offset, 0, size);

		mDirty = true;
	}

	void GpuParamBlock::updateBuffer()
	{
		if(mDirty)
		{
			mDirty = false;

			// Need to copy the data, as non-render threads might modify
			// the data before render thread has a chance to process it
			// TODO - Use an allocator
			UINT8* dataCopy = CM_NEW_BYTES(mSize, ScratchAlloc);
			memcpy(dataCopy, mData, mSize);

			queueGpuCommand(getThisPtr(), boost::bind(&GpuParamBlock::updateBuffer_internal, this, dataCopy));
		}
	}

	void GpuParamBlock::updateBuffer_internal(UINT8* data)
	{
		assert(mBuffer != nullptr);

		mBuffer->writeAll(data);

		CM_DELETE_BYTES(data, ScratchAlloc);
	}

	GpuParamBlockPtr GpuParamBlock::create(const GpuParamBlockDesc& desc)
	{
		return HardwareBufferManager::instance().createGpuParamBlock(desc);
	}
}