/**
 * \file    Mimic_Buffer.hpp
 * \author  Silvan Murer
 * \date    13 October 2022
 *
 * \brief   Class get access to the buffer component defined by GenTL
 */

#include "Mimic_Buffer.hpp"
#include "GTL_Producer.hpp"

#include "PFNC.h"

#include "spdlog/spdlog.h"
#ifdef _WIN32
#include "spdlog/sinks/msvc_sink.h"
#else
#include "spdlog/sinks/stdout_color_sinks.h"
#endif

using namespace GenTL;
using namespace genSDK;

namespace mimic {

Mimic_Buffer::Mimic_Buffer(std::shared_ptr<GTL_Producer> gtl_producer, GenTL::DEV_HANDLE dsH, GenTL::DEV_HANDLE bufH)
	: gtl_producer(gtl_producer), dsH(dsH), bufH(bufH)
{
#ifdef _WIN32
	auto windbg_sink = std::make_shared<spdlog::sinks::windebug_sink_mt>();
	//windbg_sink->set_level(spdlog::level::debug);
	logger = std::make_shared<spdlog::logger>("Mimic_Buffer", windbg_sink);
	logger->set_level(spdlog::level::debug);
#else
	logger = spdlog::stdout_color_mt("Mimic_Buffer");
#endif // _WIN32
	logger->debug("ctor call. {0}, {1}", dsH, bufH);
}

Mimic_Buffer::Mimic_Buffer(Mimic_Buffer&& other) noexcept
{
	logger = other.logger;
	other.logger.reset();
	gtl_producer = other.gtl_producer;
	other.gtl_producer.reset();
	dsH = other.dsH;
	other.dsH = GENTL_INVALID_HANDLE;
	bufH = other.bufH;
	other.bufH = GENTL_INVALID_HANDLE;
}

Mimic_Buffer::~Mimic_Buffer()
{
	release();
}

void Mimic_Buffer::release()
{
	logger->debug("release call.");

	if (bufH != GENTL_INVALID_HANDLE) {
		gtl_producer->pDSQueueBuffer(dsH, bufH);
		bufH = GENTL_INVALID_HANDLE;
	}
}

int64_t Mimic_Buffer::getPayloadType()
{
	INFO_DATATYPE dataType;
	int64_t payloadType = 0;
	size_t bufferSize = sizeof(payloadType);
	GC_ERROR err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_PAYLOADTYPE, &dataType, &payloadType, &bufferSize);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo", err);
	}
	return payloadType;
}

void* Mimic_Buffer::getBufferBase(int64_t index)
{
	GC_ERROR err;
	INFO_DATATYPE dataType;
	void* bufferBase = 0;
	size_t pSize = sizeof(bufferBase);

	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_BASE, &dataType, &bufferBase, &pSize);
	}
	else {
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_BASE, &dataType, &bufferBase, &pSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error when reading buffer base in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return bufferBase;
}
size_t Mimic_Buffer::getBufferSize(int64_t index)
{
	GC_ERROR err;
	INFO_DATATYPE dataType;
	size_t bufferSize = 0;
	size_t pSize = sizeof(bufferSize);

	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_SIZE, &dataType, &bufferSize, &pSize);
	}
	else {
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_DATA_SIZE, &dataType, &bufferSize, &pSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error when reading buffer size in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return bufferSize;
}
size_t Mimic_Buffer::getChunkSize()
{
	GC_ERROR err;
	INFO_DATATYPE dataType;
	size_t chunkSize = 0;
	size_t pSize = sizeof(chunkSize);
	err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_DELIVERED_CHUNKPAYLOADSIZE, &dataType, &chunkSize, &pSize);
	if (err != GC_ERR_SUCCESS) {
		return 0;
	}
	return chunkSize;
}

int64_t Mimic_Buffer::getNumParts()
{
	if (getPayloadType() == PAYLOAD_TYPE_IMAGE) {
		/* the buffer contains just a single image (not multipart) */
		return 1;
	}

	uint32_t numParts = 0;
	GC_ERROR err = gtl_producer->pDSGetNumBufferParts(dsH, bufH, &numParts);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo", err);
	}
	return numParts;
}

int64_t Mimic_Buffer::getNumChannels(int64_t index)
{
	throw Mimic_Exception("This method is not implemented now...", -1);
}

int64_t Mimic_Buffer::getPartPixelformat(int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}

	GC_ERROR err;
	INFO_DATATYPE dataType;
	uint64_t partDataFormat = 0;
	size_t bufferSize = sizeof(partDataFormat);
	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_PIXELFORMAT, &dataType, &partDataFormat, &bufferSize);
	}
	else {
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_DATA_FORMAT, &dataType, &partDataFormat, &bufferSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return static_cast<int64_t>(partDataFormat);
}

std::string Mimic_Buffer::getPartPixelformatName(int64_t index)
{
	return std::string(GetPixelFormatName(static_cast<PfncFormat>(getPartPixelformat(index))));
}

int64_t Mimic_Buffer::getPartWidth(int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}

	GC_ERROR err;
	INFO_DATATYPE dataType;
	size_t partWidth = 0;
	size_t bufferSize = 0;
	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		bufferSize = sizeof(partWidth);
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_WIDTH, &dataType, &partWidth, &bufferSize);
	}
	else {
		bufferSize = sizeof(partWidth);
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_WIDTH, &dataType, &partWidth, &bufferSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return static_cast<int64_t>(partWidth);
}

int64_t Mimic_Buffer::getPartHeight(int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}

	GC_ERROR err;
	INFO_DATATYPE dataType;
	size_t partHeight = 0;
	size_t bufferSize = 0;
	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		bufferSize = sizeof(partHeight);
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_HEIGHT, &dataType, &partHeight, &bufferSize);
	}
	else {
		bufferSize = sizeof(partHeight);
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_HEIGHT, &dataType, &partHeight, &bufferSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return static_cast<int64_t>(partHeight);
}

int64_t Mimic_Buffer::getPartPaddingX(int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}

	GC_ERROR err;
	INFO_DATATYPE dataType;
	size_t paddingX = 0;
	size_t bufferSize = 0;
	int64_t payloadType = getPayloadType();
	if (payloadType == PAYLOAD_TYPE_IMAGE) {
		bufferSize = sizeof(paddingX);
		err = gtl_producer->pDSGetBufferInfo(dsH, bufH, BUFFER_INFO_XPADDING, &dataType, &paddingX, &bufferSize);
	}
	else {
		bufferSize = sizeof(paddingX);
		err = gtl_producer->pDSGetBufferPartInfo(dsH, bufH, (uint32_t)index, BUFFER_PART_INFO_XPADDING, &dataType, &paddingX, &bufferSize);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSGetBufferInfo or pDSGetBufferPartInfo", err);
	}
	return static_cast<int64_t>(paddingX);
}

template<typename T>
std::vector<T> Mimic_Buffer::getPartData(int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}



	int64_t pixelformat = getPartPixelformat(index);
	int64_t width = getPartWidth(index);
	int64_t height = getPartHeight(index);
	// right now, ignore (x)padding

	int64_t pixelSize_bit = PFNC_PIXEL_SIZE(pixelformat);

	if (PFNC_IS_PIXEL_SINGLE_COMPONENT(pixelformat)) {
		// data type size must match with the pixelformat size...
		if (sizeof(T) != (int)(pixelSize_bit / 8)) {
			throw Mimic_Exception("Data type error...", -400);
		}
	}

	void* bufferBase = getBufferBase(index);
	size_t bufferSize = getBufferSize(index);

	size_t numPixels = width * height;
	return std::vector<T>(static_cast<T*>(bufferBase), static_cast<T*>(bufferBase) + numPixels);
}

template<typename T>
void Mimic_Buffer::getPartData(T* buffer, size_t* bufferSize, size_t paddingX, int64_t index)
{
	int64_t numParts = getNumParts();
	if ((index < 0) || (index >= numParts)) {
		throw Mimic_OutOfRange_Exception("Part index out of range", -1200);
	}

	int64_t pixelformat = getPartPixelformat(index);
	int64_t width = getPartWidth(index);
	int64_t height = getPartHeight(index);
	int64_t bufferPaddingX = getPartPaddingX(index);

	int64_t pixelSize_bit = PFNC_PIXEL_SIZE(pixelformat);

	if (PFNC_IS_PIXEL_SINGLE_COMPONENT(pixelformat)) {
		// data type size must match with the pixelformat size...
		if (sizeof(T) != (int)(pixelSize_bit / 8)) {
			throw Mimic_Exception("Data type error...", -400);
		}
	}

	// external buffer must be compatible with internal buffer...
	size_t newBufferSize = height * (width * sizeof(T) + paddingX);
	if (*bufferSize < newBufferSize) {
		throw Mimic_Exception("Allocated buffer is too small", -400);
	}
	*bufferSize = newBufferSize;

	void* bufferBase = getBufferBase(index);

	if (paddingX == bufferPaddingX) {
		memcpy(buffer, bufferBase, newBufferSize);
	}
	else {
		size_t usefulSize = width * sizeof(T);
		size_t pitch = usefulSize + paddingX;
		size_t bufferPitch = usefulSize + bufferPaddingX;

		for (int64_t i = 0; i < height; i++) {
			memcpy(((uint8_t*)buffer) + i * pitch, ((uint8_t*)bufferBase) + i * bufferPitch, usefulSize);
		}
	}
}

template<typename T>
std::vector<T> Mimic_Buffer::getChannelData(int64_t index, int64_t channel)
{
	throw Mimic_Exception("This method is not implemented now...", -1);
}

} /* namespace genSDK */
