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

#include "Mimic_Camera.hpp"
#include "GTL_Producer.hpp"
#include "M_Xml.hpp"

#include "GenApi/EventAdapterGeneric.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;

namespace mimic {

Mimic_Camera::Mimic_Camera(std::shared_ptr<genSDK::GTL_Producer> gtl_producer, GenTL::DEV_HANDLE devH)
 : gtl_producer(gtl_producer), devH(devH)
{
#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_Camera", windbg_sink);
	logger->set_level(spdlog::level::debug);
#else
	logger = spdlog::stdout_color_mt("Mimic_Camera");
#endif // _WIN32
	logger->debug("ctor call. {0}", devH);

	/* create data stream used for data acquisition (right now, just one is supported) */
	uint32_t nofDataStrams = 0;
	GC_ERROR err = gtl_producer->pDevGetNumDataStreams(devH, &nofDataStrams);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("could not read the number of available data streams!", err);
	}
	if (nofDataStrams == 0) {
		throw Mimic_Exception("No data stream is available on this device!", -600);
	}
	else if (nofDataStrams > 1) {
		logger->warn("there are {0} data streams available. Use just the fist one.", nofDataStrams);
	}

	size_t bufSize = 0;
	gtl_producer->pDevGetDataStreamID(devH, 0, nullptr, &bufSize);
	
	std::string streamName(bufSize, '\0');
	bufSize = streamName.size();
	err = gtl_producer->pDevGetDataStreamID(devH, 0, &streamName.front(), &bufSize);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("could not read data stream name!", err);
	}
	logger->info("Stream Name: {0}", streamName);

	err = gtl_producer->pDevOpenDataStream(devH, streamName.c_str(), &dsH);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("could not open data stream!", err);
	}

	/* create port for the 'remote device' */
	PORT_HANDLE rmtDevHdl = GENTL_INVALID_HANDLE;
	err = gtl_producer->pDevGetPort(devH, &rmtDevHdl);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("could not initialize remote device port!", err);
	}

	/* read 'device description (xml)' */
	uint32_t nofXMLs = M_Xml::getNofXMLs(gtl_producer, rmtDevHdl);
	if (nofXMLs == 0) {
		throw Mimic_Exception("No XML file found ont the interface component!", -1);
	}
	else if (nofXMLs > 1) {
		logger->info("Multiple ({0}) XML files are found on the interface component. Select '0'", nofXMLs);
	}
	uint32_t xmlId = 0;
	std::string xmlName;
	uint64_t xmlAddress = 0;
	uint64_t xmlSize = 0;
	M_Xml::getXMLInfo(gtl_producer, rmtDevHdl, xmlId, xmlName, xmlAddress, xmlSize);
	logger->info("Device XML File {0} @{1}:{2}", xmlName, xmlAddress, xmlSize);
	std::string xmlContent = M_Xml::getXMLContent(gtl_producer, rmtDevHdl, xmlAddress, xmlSize);

	/* read device port name */
	INFO_DATATYPE dataType;
	bufSize = 0;
	gtl_producer->pGCGetPortInfo(rmtDevHdl, PORT_INFO_PORTNAME, &dataType, nullptr, &bufSize);

	std::string portName(bufSize, '\0');
	bufSize = portName.size();
	err = gtl_producer->pGCGetPortInfo(rmtDevHdl, PORT_INFO_PORTNAME, &dataType, &portName.front(), &bufSize);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("could not read interface port name!", err);
	}
	logger->info("Device Port: {0}", portName);

	/* create 'device node map' */
	nodeMap = Mimic_NodeMap(gtl_producer, rmtDevHdl, portName, xmlContent);

	/* device events */
	eventAdapter = std::make_shared< GenApi_3_4::CEventAdapterGeneric>(nodeMap.getRawINodeMap());
	newBufEvtH = GENTL_INVALID_HANDLE;
	inAcquisition = false;

}

Mimic_Camera::Mimic_Camera(Mimic_Camera&& other) noexcept
{
	logger = other.logger;
	//other.logger.reset();
	logger->debug("assignment ctor call.");
	gtl_producer = other.gtl_producer;
	other.gtl_producer.reset();
	devH = other.devH;
	other.devH = GENTL_INVALID_HANDLE;
	dsH = other.dsH;
	other.dsH = GENTL_INVALID_HANDLE;
	newBufEvtH = other.newBufEvtH;
	other.newBufEvtH = GENTL_INVALID_HANDLE;
	bufHVec = other.bufHVec;
	other.bufHVec.clear();
	eventAdapter = other.eventAdapter;
	other.eventAdapter.reset();
	inAcquisition = other.inAcquisition;
	other.inAcquisition = false;

	nodeMap = other.nodeMap;
	other.nodeMap = Mimic_NodeMap();
}

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


void Mimic_Camera::release()
{
	if (logger) {
		logger->debug("release call.");
	}
	if (inAcquisition) {
		stopAcquisition();
	}
	if (eventAdapter) {
		eventAdapter->DetachNodeMap();
		eventAdapter.reset();
	}

	nodeMap = Mimic_NodeMap();

	if (dsH != GENTL_INVALID_HANDLE) {
		gtl_producer->pDSClose(dsH);
		dsH = GENTL_INVALID_HANDLE;
	}
	if (devH != GENTL_INVALID_HANDLE) {
		gtl_producer->pDevClose(devH);
		devH = GENTL_INVALID_HANDLE;
	}

}

std::string Mimic_Camera::getDeviceName()
{
	logger->debug("getDeviceName() call.");
	try {
		GenTL::GC_ERROR err;
		INFO_DATATYPE dataType;
		size_t bufSize = 0;
		gtl_producer->pDevGetInfo(devH, DEVICE_INFO_ID, &dataType, nullptr, &bufSize);
		std::vector<char> buf(bufSize);
		bufSize = buf.size();
		err = gtl_producer->pDevGetInfo(devH, DEVICE_INFO_ID, &dataType, buf.data(), &bufSize);
		if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error", err);
		}
		return std::string(buf.data());
	}
	catch (const std::bad_function_call& e) {
		std::string exMsg = std::string("GenTL Producer function not initialized (pGCGetInfo)! ") + e.what();
		logger->error(exMsg);
		throw Mimic_GenTL_Exception(exMsg, -500);
	}
	catch (const Mimic_GenTL_Exception& e) {
		throw e;
	}
	catch (...) {
		logger->error("Unknown exception when call getVersion!");
		throw; // throw the exception to the user
	}
}

void Mimic_Camera::startAcquisition(int64_t nofBuf)
{
	if (inAcquisition) {
		throw Mimic_Exception("Acquisition already run!", -1000);
	}
	inAcquisition = true;

	if (nofBuf <= 0) {
		throw Mimic_Exception("At least one buffer must be allocated!", -1010);
	}

	/* read expected payload size from data stream */
	INFO_DATATYPE dataType;
	bool dsDefinesPayloadSize;
	size_t bufferSize = sizeof(dsDefinesPayloadSize);
	GC_ERROR err = gtl_producer->pDSGetInfo(dsH, STREAM_INFO_DEFINES_PAYLOADSIZE, &dataType, &dsDefinesPayloadSize, &bufferSize);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error", err);
	}
	size_t payloadSize = 0;
	if (dsDefinesPayloadSize) {
		bufferSize = sizeof(payloadSize);
		err = gtl_producer->pDSGetInfo(dsH, STREAM_INFO_PAYLOAD_SIZE, &dataType, &payloadSize, &bufferSize);
		if (err != GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error", err);
		}
	}
	else {
		// read the PayloadSize from the remote device
		payloadSize = nodeMap.getValue<int64_t>("PayloadSize");
	}
	logger->debug("PayloadSize {0}", payloadSize);

	bufHVec.clear();
	bufHVec.reserve(nofBuf);
	for (int64_t i = 0; i < nofBuf; i++) {
		BUFFER_HANDLE bufH = GENTL_INVALID_HANDLE;
		/* allocate and announce buffer */
		err = gtl_producer->pDSAllocAndAnnounceBuffer(dsH, payloadSize, nullptr, &bufH);
		if (err != GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error in pDSAllocAndAnnounceBuffer", err);
		}
		/* queue buffer (to input queue) */
		err = gtl_producer->pDSQueueBuffer(dsH, bufH);
		if (err != GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error in pDSQueueBuffer", err);
		}
		bufHVec.push_back(bufH);
	}

	/* registrate for buffer event (may create thread in a future implementation) */
	newBufEvtH = GENTL_INVALID_HANDLE;
	err = gtl_producer->pGCRegisterEvent(dsH, EVENT_NEW_BUFFER, &newBufEvtH);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pGCRegisterEvent", err);
	}

	/* start acquisition */
	nodeMap.setValue("TLParamsLocked", 1);
	err = gtl_producer->pDSStartAcquisition(dsH, ACQ_START_FLAGS_DEFAULT, GENTL_INFINITE);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSStartAcquisition", err);
	}
	nodeMap.setValue("AcquisitionStart", 1);
}

void Mimic_Camera::stopAcquisition()
{
	inAcquisition = false;

	/* stop acquisition */
	nodeMap.setValue("AcquisitionStop", 1);
	GC_ERROR err = gtl_producer->pDSStopAcquisition(dsH, ACQ_STOP_FLAGS_DEFAULT);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSStopAcquisition", err);
	}
	nodeMap.setValue("TLParamsLocked", 0);

	/* flush input output buffer queue and events */
	err = gtl_producer->pDSFlushQueue(dsH, ACQ_QUEUE_ALL_DISCARD);
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pDSFlushQueue", err);
	}
	if (newBufEvtH != GENTL_INVALID_HANDLE) {
		err = gtl_producer->pEventFlush(newBufEvtH);
		if (err != GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error in pEventFlush", err);
		}
	}
	newBufEvtH = GENTL_INVALID_HANDLE;

	err = gtl_producer->pGCUnregisterEvent(dsH, EVENT_NEW_BUFFER);
	if (err != GC_ERR_SUCCESS) { //& (err != GC_ERR_NOT_INITIALIZED)
		throw Mimic_GenTL_Exception("GenTL error in pGCUnregisterEvent", err);
	}

	for (auto bufH : bufHVec) {
		err = gtl_producer->pDSRevokeBuffer(dsH, bufH, NULL, NULL);
		if (err != GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error in pDSRevokeBuffer", err);
		}
	}
	bufHVec.clear();
}

Mimic_Buffer Mimic_Camera::getBuffer(uint32_t timeout_ms)
{
	EVENT_NEW_BUFFER_DATA eventData;
	size_t eventSize = sizeof(eventData);
	GC_ERROR err = gtl_producer->pEventGetData(newBufEvtH, &eventData, &eventSize, timeout_ms);
	if (err == GC_ERR_TIMEOUT) {
		throw Mimic_Timeout_Exception("Timeout when waiting for a new data buffer. No data available!", err);
	}
	if (err != GC_ERR_SUCCESS) {
		throw Mimic_GenTL_Exception("GenTL error in pEventGetData", err);
	}

	return Mimic_Buffer(gtl_producer, dsH, eventData.BufferHandle);
}

} /* namespace genSDK */
