/**
 * \file    Mimic_Producer.hpp
 * \author  Silvan Murer
 * \date    11 October 2022
 *
 * \brief   Mimic_Producer - Class for accessing and handling a single GenICam producer
 */

#include "Mimic_Producer.hpp"
#include "GTL_Producer.hpp"

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

namespace fs = std::filesystem;

namespace mimic {

Mimic_Producer::Mimic_Producer()
{
#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_Producer", windbg_sink);
	logger->set_level(spdlog::level::debug);
#else
	logger = spdlog::stdout_color_mt("Mimic_Producer");
#endif // _WIN32
	logger->debug("default ctor call.");

	gtl_producer.reset();
	tlHandle = GENTL_INVALID_HANDLE;
}
Mimic_Producer::Mimic_Producer(Mimic_Producer&& other)
{
	logger = other.logger;
	other.logger.reset();

	gtl_producer = other.gtl_producer;
	other.gtl_producer.reset();

	tlHandle = other.tlHandle;
	other.tlHandle = GENTL_INVALID_HANDLE;
}
Mimic_Producer& Mimic_Producer::operator=(Mimic_Producer&& other)
{
	if (this != &other)
	{
		// free the resources of the current object
		release();

		logger = other.logger;
		other.logger.reset();

		gtl_producer = other.gtl_producer;
		other.gtl_producer.reset();

		tlHandle = other.tlHandle;
		other.tlHandle = GENTL_INVALID_HANDLE;
	}
	return *this;
}

Mimic_Producer::Mimic_Producer(const fs::path producer)
{
#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_Producer", windbg_sink);
	logger->set_level(spdlog::level::debug);
#else
	logger = spdlog::stdout_color_mt("Mimic_Producer");
#endif // _WIN32
	logger->debug("ctor call. {0}", producer.string());
	try {
		gtl_producer = std::make_shared<genSDK::GTL_Producer>(producer.string());
		gtl_producer->pGCInitLib();
		tlHandle = GENTL_INVALID_HANDLE;
		gtl_producer->pTLOpen(&tlHandle);
	}
	catch (const std::bad_function_call& e) {
		std::string exMsg = std::string("GenTL Producer function not initialized (GCInitLib)! ") + e.what();
		logger->error(exMsg);
		throw Mimic_GenTL_Exception(exMsg, -500);
	}
	catch (...) {
		logger->error("Unknown exception when loading or initializing GenTL producer!");
		throw; // throw the exception to the user
	}
}

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

void Mimic_Producer::release()
{
	if (logger) {
		logger->debug("release call.");
	}
	try {
		if (gtl_producer) {
			gtl_producer->pTLClose(tlHandle);
			tlHandle = GENTL_INVALID_HANDLE;
			gtl_producer->pGCCloseLib();
		}
	}
	catch (const std::bad_function_call& e) {
		logger->error("GenTL Producer function not initialized (pGCCloseLib)! {0}", e.what());
	}
	catch (...) {
		logger->error("Unknown exception when closing GenTL producer!");
	}

	gtl_producer.reset();
	logger.reset();
}
void Mimic_Producer::checkObject()
{
	if (!gtl_producer || !logger || (tlHandle == GENTL_INVALID_HANDLE)) {
		throw Mimic_Exception("Mimic_Producer object not correctly initialized!", -100);
	}
}

std::string Mimic_Producer::getVersion()
{
	checkObject();
	logger->debug("getVersion call.");
	try {
		GenTL::INFO_DATATYPE dataType;
		size_t bufSize = 0;
		gtl_producer->pGCGetInfo(GenTL::TL_INFO_VERSION, &dataType, nullptr, &bufSize);
		std::vector<char> buf(bufSize);
		bufSize = buf.size();
		gtl_producer->pGCGetInfo(GenTL::TL_INFO_VERSION, &dataType, buf.data(), &bufSize);
		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 (...) {
		logger->error("Unknown exception when call getVersion!");
		throw; // throw the exception to the user
	}
}
std::string Mimic_Producer::getLocation()
{
	checkObject();
	logger->debug("getLocation call.");
	try {
		GenTL::INFO_DATATYPE dataType;
		size_t bufSize = 0;
		gtl_producer->pGCGetInfo(GenTL::TL_INFO_PATHNAME, &dataType, nullptr, &bufSize);
		std::vector<char> buf(bufSize);
		bufSize = buf.size();
		gtl_producer->pGCGetInfo(GenTL::TL_INFO_PATHNAME, &dataType, buf.data(), &bufSize);
		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 (...) {
		logger->error("Unknown exception when call getLocation!");
		throw; // throw the exception to the user
	}
}

uint32_t Mimic_Producer::update(uint32_t timeout)
{
	checkObject();
	logger->debug("update call.");
	try {
		bool8_t bChanged;
		GenTL::GC_ERROR err;
		logger->debug("update interface list");
		err = gtl_producer->pTLUpdateInterfaceList(tlHandle, &bChanged, timeout);
		if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error", err);
		}
		uint32_t nofInterfaces;
		err = gtl_producer->pTLGetNumInterfaces(tlHandle, &nofInterfaces);
		if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error", err);
		}

		logger->debug("clear and update internal interface list");
		device_list.clear();

		/* populate device_list */
		for (uint32_t i = 0; i < nofInterfaces; i++) {
			try {
				size_t bufSize = 0;
				gtl_producer->pTLGetInterfaceID(tlHandle, i, nullptr, &bufSize);
				std::vector<char> buf(bufSize);
				bufSize = buf.size();
				err = gtl_producer->pTLGetInterfaceID(tlHandle, i, buf.data(), &bufSize);
				if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
					throw Mimic_GenTL_Exception("GenTL error", err);
				}
				std::string interfaceName = std::string(buf.data(), buf.size());

				GenTL::IF_HANDLE ifHandle = GENTL_INVALID_HANDLE;
				err = gtl_producer->pTLOpenInterface(tlHandle, interfaceName.c_str(), &ifHandle);
				if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
					throw Mimic_GenTL_Exception("GenTL error", err);
				}
				
				/* scan for available devices */
				err = gtl_producer->pIFUpdateDeviceList(ifHandle, &bChanged, timeout);
				if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
					throw Mimic_GenTL_Exception("GenTL error", err);
				}
				uint32_t nofDevices;
				err = gtl_producer->pIFGetNumDevices(ifHandle, &nofDevices);
				if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
					throw Mimic_GenTL_Exception("GenTL error", err);
				}

				for (uint32_t j = 0; j < nofDevices; j++) {
					gtl_producer->pIFGetDeviceID(ifHandle, j, nullptr, &bufSize);
					std::vector<char> buf(bufSize);
					bufSize = buf.size();
					err = gtl_producer->pIFGetDeviceID(ifHandle, j, buf.data(), &bufSize);
					if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
						throw Mimic_GenTL_Exception("GenTL error", err);
					}

					DeviceInfo devInfo = DeviceInfo(ifHandle, std::string(buf.data()));
					device_list.push_back(devInfo);
				}				
			}
			catch (const std::bad_function_call& e) {
				logger->error("Interface with ID " + std::to_string(i) + " not appendet, internal bad_function_call error: " + e.what());
			}
			catch (const Mimic_GenTL_Exception& e) {
				logger->error("Interface with ID " + std::to_string(i) + " not appendet, internal Mimic_GenTL_Exception error: " + e.what());
			}
			catch (...) {
				logger->error("Interface with ID " + std::to_string(i) + " not appendet, unknown internal error!");
			}
		}
		return device_list.size();
	}
	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 update!");
		throw; // throw the exception to the user
	}
}

std::string Mimic_Producer::getCameraName(uint32_t camNo)
{
	checkObject();
	logger->debug("getCameraName({0}) call.", camNo);

	if (camNo >= device_list.size()) {
		throw Mimic_OutOfRange_Exception("The selected camera ID is out of range. Try to use update() and check for availabel devices.", -200);
	}
	return device_list.at(camNo).cameraName;
}

Mimic_Camera Mimic_Producer::openCamera(uint32_t camNo)
{
	checkObject();
	logger->debug("openCamera({0}) call.", camNo);

	try {
		if(camNo >= device_list.size()) {
			throw Mimic_OutOfRange_Exception("The selected camera ID is out of range. Try to use update() and check for availabel devices.", -200);
		}
		DeviceInfo devInfo = device_list.at(camNo);

		GenTL::IF_HANDLE devHandle = GENTL_INVALID_HANDLE;
		GenTL::GC_ERROR err = gtl_producer->pIFOpenDevice(devInfo.ifHandle, devInfo.cameraName.c_str(), GenTL::DEVICE_ACCESS_EXCLUSIVE, &devHandle);
		if (err != GenTL::GC_ERROR_LIST::GC_ERR_SUCCESS) {
			throw Mimic_GenTL_Exception("GenTL error", err);
		}
		return Mimic_Camera(gtl_producer, devHandle);
	}
	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 openCamera!");
		throw; // throw the exception to the user
	} 
}

} /* namespace mimic */
