/*  \file     test_viky.cpp
 *  \brief    mimic core test for device configuration
 *            based on the static library, the test tries
 *            to read and write configurations
 *  \version  1.0.0
 *  \author   Silvan Murer
 *  \date     2022
 */

#include "Mimic.hpp"

#include <gtest/gtest.h>

#include <algorithm>
#include <iostream>

using namespace mimic;

/*
 * Expected NodeMap of viky:
 * 
 * - Root
 *   - TransportLayerControl
 *       TLParamsLocked
 *       PayloadSize
 *       GenDCStreamingMode
 *       GenDCFlowMappingTable
 *       GenDCDescriptor
 *   - DeviceControl
 *       DeviceFeaturePersistenceStart
 *       DeviceFeaturePersistenceEnd
 *       DeviceRegistersStreamingStart
 *       DeviceRegistersStreamingEnd
 *   - ImageFormatControl
 *       Width
 *       Height
 *       AspectRatio
 *       ComponentSelector
 *       ComponentEnable
 *       PixelFormat
 *       ComponentIDValue
 *   - AcquisitionControl
 *       AcquisitionMode
 *       AcquisitionStart
 *       AcquisitionStop
 *       AcquisitionFrameRate
 *   - ChunkDataControl
 *       ChunkModeActive
 *       ChunkSelector
 *       ChunkEnable
 *       ChunkNrBounces
 *       ChunkComponentSelector
 *       ChunkComponentIDValue
 *       ChunkScan3dDistanceUnit
 *       ChunkScan3dOutputMode
 *       ChunkScan3dCoordinateSystem
 *       ChunkScan3dCoordinateSystemReference
 *       ChunkScan3dCoordinateSelector
 *       ChunkScan3dCoordinateScale
 *       ChunkScan3dCoordinateOffset
 *       ChunkScan3dInvalidDataFlag
 *       ChunkScan3dCoordinateReferenceSelector
 *       ChunkScan3dCoordinateReferenceValue
 *   - EventControl
 *       EventSelector
 *       EventNotification
 *       EventGameProgressData
 *   - FunnyFeaturesControl
 *       BouncingBallSize
 *       FancyBallOperation
 *   - Scan3dControl
 *       Scan3dDistanceUnit
 *       Scan3dCoordinateSystem
 *       Scan3dOutputMode
 *       Scan3dCoordinateSystemReference
 *       Scan3dCoordinateSelector
 *       Scan3dCoordinateScale
 *       Scan3dCoordinateOffset
 *       Scan3dInvalidDataFlag
 *       Scan3dCoordinateReferenceSelector
 *       Scan3dCoordinateReferenceValue
 */
TEST(ConfigureTest, NodeMap) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto rootNodePtr = camera->nodeMap.getNode("Root");
	ASSERT_NE(rootNodePtr, nullptr);
	ASSERT_EQ(rootNodePtr->getName(), "Root");
	ASSERT_EQ(rootNodePtr->getType(), Mimic_Node::Type_e::CATEGORY);
	auto rootCategoryPtr = std::dynamic_pointer_cast<Mimic_Category>(rootNodePtr);
	ASSERT_NE(rootCategoryPtr, nullptr);
	auto rootFeatures = rootCategoryPtr->getFeatures();
	EXPECT_EQ(rootFeatures.size(), 8);

	/* lambda to find a category feature in the vector given by 'getFeatures' */
	auto findCategory = [](const std::vector<std::shared_ptr<Mimic_Node>>& list, std::string name) {
		auto nodeItr = std::find_if(list.begin(), list.end(), [&](const auto& obj) {return obj->getName() == name; });
		return (nodeItr != list.end()) ? std::dynamic_pointer_cast<Mimic_Category>(*nodeItr) : nullptr;
	};
	/* lambda to find a feature in the vector given by 'getFeatures' */
	auto findFeature = [](const std::vector<std::shared_ptr<Mimic_Node>>& list, std::string name) {
		auto nodeItr = std::find_if(list.begin(), list.end(), [&](const auto& obj) {return obj->getName() == name; });
		return (nodeItr != list.end()) ? *nodeItr : nullptr;
	};

	auto transportLayerControl = findCategory(rootFeatures, "TransportLayerControl");
	EXPECT_NE(transportLayerControl, nullptr);
	auto tlcFeatures = transportLayerControl->getFeatures();
	EXPECT_EQ(tlcFeatures.size(), 5);
	for (std::string ftr : { "TLParamsLocked", "PayloadSize", "GenDCStreamingMode", "GenDCFlowMappingTable", "GenDCDescriptor" }) {
		auto ftrPtr = findFeature(tlcFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto deviceControl = findCategory(rootFeatures, "DeviceControl");
	EXPECT_NE(deviceControl, nullptr);
	auto dcFeatures = deviceControl->getFeatures();
	EXPECT_EQ(dcFeatures.size(), 4);
	for (std::string ftr : { "DeviceFeaturePersistenceStart", "DeviceFeaturePersistenceEnd", "DeviceRegistersStreamingStart", "DeviceRegistersStreamingEnd" }) {
		auto ftrPtr = findFeature(dcFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto imageFormatControl = findCategory(rootFeatures, "ImageFormatControl");
	EXPECT_NE(imageFormatControl, nullptr);
	auto ifcFeatures = imageFormatControl->getFeatures();
	EXPECT_EQ(ifcFeatures.size(), 7);
	for (std::string ftr : { "Width", "Height", "AspectRatio", "ComponentSelector", "ComponentEnable", "PixelFormat", "ComponentIDValue" }) {
		auto ftrPtr = findFeature(ifcFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto acquisitionControl = findCategory(rootFeatures, "AcquisitionControl");
	EXPECT_NE(acquisitionControl, nullptr);
	auto acFeatures = acquisitionControl->getFeatures();
	EXPECT_EQ(acFeatures.size(), 4);
	for (std::string ftr : { "AcquisitionMode", "AcquisitionStart", "AcquisitionStop", "AcquisitionFrameRate" }) {
		auto ftrPtr = findFeature(acFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto chunkDataControl = findCategory(rootFeatures, "ChunkDataControl");
	EXPECT_NE(chunkDataControl, nullptr);
	auto cdcFeatures = chunkDataControl->getFeatures();
	EXPECT_EQ(cdcFeatures.size(), 16);
	for (std::string ftr : { "ChunkModeActive", "ChunkSelector", "ChunkEnable", "ChunkNrBounces", "ChunkComponentSelector", 
		"ChunkComponentIDValue", "ChunkScan3dDistanceUnit", "ChunkScan3dOutputMode", "ChunkScan3dCoordinateSystem",
		"ChunkScan3dCoordinateSystemReference", "ChunkScan3dCoordinateSelector", "ChunkScan3dCoordinateScale", 
		"ChunkScan3dCoordinateOffset", "ChunkScan3dInvalidDataFlag", "ChunkScan3dCoordinateReferenceSelector", 
		"ChunkScan3dCoordinateReferenceValue"}) {
		auto ftrPtr = findFeature(cdcFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto eventControl = findCategory(rootFeatures, "EventControl");
	EXPECT_NE(eventControl, nullptr);
	auto ecFeatures = eventControl->getFeatures();
	EXPECT_EQ(ecFeatures.size(), 3);
	for (std::string ftr : { "EventSelector", "EventNotification", "EventGameProgressData" }) {
		auto ftrPtr = findFeature(ecFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}
	
	auto funnyFeaturesControl = findCategory(rootFeatures, "FunnyFeaturesControl");
	EXPECT_NE(funnyFeaturesControl, nullptr);
	auto ffcFeatures = funnyFeaturesControl->getFeatures();
	EXPECT_EQ(ffcFeatures.size(), 2);
	for (std::string ftr : { "BouncingBallSize", "FancyBallOperation" }) {
		auto ftrPtr = findFeature(ffcFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	auto scan3dControl = findCategory(rootFeatures, "Scan3dControl");
	EXPECT_NE(scan3dControl, nullptr);
	auto s3cFeatures = scan3dControl->getFeatures();
	EXPECT_EQ(s3cFeatures.size(), 10);
	for (std::string ftr : { "Scan3dDistanceUnit", "Scan3dCoordinateSystem", "Scan3dOutputMode", 
		"Scan3dCoordinateSystemReference", "Scan3dCoordinateSelector", "Scan3dCoordinateScale", 
		"Scan3dCoordinateOffset", "Scan3dInvalidDataFlag", "Scan3dCoordinateReferenceSelector",
		"Scan3dCoordinateReferenceValue"}) {
		auto ftrPtr = findFeature(s3cFeatures, ftr);
		EXPECT_NE(ftrPtr, nullptr) << "Feature " << ftr << " not found!";
	}

	camera->release();
}


/* Check Properties of Integer Feature 'Width' */
TEST(ConfigureTest, IntegerFeature) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto widthNodePtr = camera->nodeMap.getNode("Width");
	ASSERT_NE(widthNodePtr, nullptr);
	ASSERT_EQ(widthNodePtr->getName(), "Width");
	ASSERT_EQ(widthNodePtr->getType(), Mimic_Node::Type_e::INTEGER);
	auto widthIntPtr = std::dynamic_pointer_cast<Mimic_Integer>(widthNodePtr);
	ASSERT_NE(widthIntPtr, nullptr);
	EXPECT_EQ(widthIntPtr->getName(), "Width");
	ASSERT_EQ(widthIntPtr->getType(), Mimic_Node::Type_e::INTEGER);
	EXPECT_EQ(widthIntPtr->getValue(), 400);

	EXPECT_EQ(widthIntPtr->isSelector(), false);
	auto selectedFtr = widthIntPtr->getSelectedFeatures();
	EXPECT_EQ(selectedFtr.size(), 0);
	auto selectingFtr = widthIntPtr->getSelectingFeatures();
	EXPECT_EQ(selectingFtr.size(), 0);

	widthIntPtr->setValue(200);
	EXPECT_EQ(widthIntPtr->getValue(), 200);

	camera->nodeMap.setValue<int64_t>("Width", 256);
	EXPECT_EQ(widthIntPtr->getValue(), 256);
}

/* Check Properties of Float Feature 'Scan3dCoordinateScale' */
TEST(ConfigureTest, FloatFeature) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto ftrNodePtr = camera->nodeMap.getNode("Scan3dCoordinateReferenceValue");
	ASSERT_NE(ftrNodePtr, nullptr);
	ASSERT_EQ(ftrNodePtr->getName(), "Scan3dCoordinateReferenceValue");
	ASSERT_EQ(ftrNodePtr->getType(), Mimic_Node::Type_e::FLOAT);
	auto ftrFloatPtr = std::dynamic_pointer_cast<Mimic_Float>(ftrNodePtr);
	ASSERT_NE(ftrFloatPtr, nullptr);
	EXPECT_EQ(ftrFloatPtr->getName(), "Scan3dCoordinateReferenceValue");
	ASSERT_EQ(ftrFloatPtr->getType(), Mimic_Node::Type_e::FLOAT);
	EXPECT_EQ(ftrFloatPtr->getValue(), 400);

	EXPECT_EQ(ftrFloatPtr->isSelector(), false);
	auto selectedFtr = ftrFloatPtr->getSelectedFeatures();
	EXPECT_EQ(selectedFtr.size(), 0);
	auto selectingFtr = ftrFloatPtr->getSelectingFeatures();
	EXPECT_EQ(selectingFtr.size(), 0);

	ftrFloatPtr->setValue(4.2);
	EXPECT_EQ(ftrFloatPtr->getValue(), 4.2);

	camera->nodeMap.setValue<double>("Scan3dCoordinateReferenceValue", 25.6);
	EXPECT_EQ(ftrFloatPtr->getValue(), 25.6);
}

/* Check Properties of Enum & Selector Feature 'ComponentSelector' */
TEST(ConfigureTest, EnumFeature) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto ftrNodePtr = camera->nodeMap.getNode("ComponentSelector");
	ASSERT_NE(ftrNodePtr, nullptr);
	ASSERT_EQ(ftrNodePtr->getName(), "ComponentSelector");
	ASSERT_EQ(ftrNodePtr->getType(), Mimic_Node::Type_e::ENUMERATION);
	auto ftrPtr = std::dynamic_pointer_cast<Mimic_Enumeration>(ftrNodePtr);
	ASSERT_NE(ftrPtr, nullptr);
	EXPECT_EQ(ftrPtr->getName(), "ComponentSelector");
	ASSERT_EQ(ftrPtr->getType(), Mimic_Node::Type_e::ENUMERATION);

	EXPECT_EQ(ftrPtr->isSelector(), true);
	auto selectedFtr = ftrPtr->getSelectedFeatures();
	EXPECT_EQ(selectedFtr.size(), 3);
	EXPECT_EQ(selectedFtr.at(0)->getName(), "ComponentEnable");
	EXPECT_EQ(selectedFtr.at(1)->getName(), "PixelFormat");
	EXPECT_EQ(selectedFtr.at(2)->getName(), "ComponentIDValue");
	auto selectingFtr = ftrPtr->getSelectingFeatures();
	EXPECT_EQ(selectingFtr.size(), 0);

	auto entryList = ftrPtr->getListOfValidValue();
	EXPECT_EQ(entryList.size(), 2);
	EXPECT_EQ(entryList.at(0), "Intensity");
	EXPECT_EQ(entryList.at(1), "Range");

	EXPECT_EQ(ftrPtr->getValue(), "Intensity");

	ftrPtr->setValue(entryList.at(1));
	EXPECT_EQ(ftrPtr->getValue(), entryList.at(1));

	camera->nodeMap.setValue<std::string>("ComponentSelector", entryList.at(0));
	EXPECT_EQ(ftrPtr->getValue(), entryList.at(0));
	EXPECT_EQ(camera->nodeMap.getValue<std::string>("ComponentSelector"), entryList.at(0));
}

/* Check Properties of Command Feature 'FancyBallOperation' */
TEST(ConfigureTest, CommandFeature) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto ftrNodePtr = camera->nodeMap.getNode("FancyBallOperation");
	ASSERT_NE(ftrNodePtr, nullptr);
	ASSERT_EQ(ftrNodePtr->getName(), "FancyBallOperation");
	ASSERT_EQ(ftrNodePtr->getType(), Mimic_Node::Type_e::COMMAND);
	auto ftrPtr = std::dynamic_pointer_cast<Mimic_Command>(ftrNodePtr);
	ASSERT_NE(ftrPtr, nullptr);
	EXPECT_EQ(ftrPtr->getName(), "FancyBallOperation");
	ASSERT_EQ(ftrPtr->getType(), Mimic_Node::Type_e::COMMAND);

	EXPECT_EQ(ftrPtr->isSelector(), false);
	auto selectedFtr = ftrPtr->getSelectedFeatures();
	EXPECT_EQ(selectedFtr.size(), 0);
	auto selectingFtr = ftrPtr->getSelectingFeatures();
	EXPECT_EQ(selectingFtr.size(), 0);

	EXPECT_EQ(ftrPtr->isDone(), true);
	ftrPtr->execute();
	// ToDo: right now, only one command is possible... Check test producer implementation
	//ftrPtr->execute();
	//EXPECT_EQ(ftrPtr->isDone(), true);
	
	//camera->nodeMap.setValue<bool>("FancyBallOperation", true);
	//EXPECT_EQ(ftrPtr->isDone(), true);
}

/* Check Properties of Boolean Feature 'ChunkModeActive' */
TEST(ConfigureTest, BoolFeature) {
	/* create mimic object, print version information and scan for available producers */
	Mimic& mime = Mimic::getInstance();
	uint32_t nofProducers = mime.update();

	/* at least one should be viky, used for this test */
	Mimic_Producer producer;
	for (uint32_t i = 0; i < nofProducers; i++) {
		if (!mime.getProducerName(i).compare("viky")) {
			producer = mime.openProducer(i);
		}
	}

	std::string vikyLocation = producer.getLocation(); // incl. file name
	std::string fileName = vikyLocation.substr(vikyLocation.size() - 8);
	ASSERT_EQ(fileName, "viky.cti");

	uint32_t nofDevices = producer.update(1800);
	std::string testDevice = "VikyTL_DEV_red";

	std::shared_ptr<Mimic_Camera> camera;
	for (uint32_t i = 0; i < nofDevices; i++) {
		if (!producer.getCameraName(i).compare(testDevice)) {
			camera = std::make_shared<Mimic_Camera>(producer.openCamera(i));
			break;
		}
	}
	ASSERT_NE(camera, nullptr);

	auto ftrNodePtr = camera->nodeMap.getNode("ChunkModeActive");
	ASSERT_NE(ftrNodePtr, nullptr);
	ASSERT_EQ(ftrNodePtr->getName(), "ChunkModeActive");
	ASSERT_EQ(ftrNodePtr->getType(), Mimic_Node::Type_e::BOOLEAN);
	auto ftrPtr = std::dynamic_pointer_cast<Mimic_Boolean>(ftrNodePtr);
	ASSERT_NE(ftrPtr, nullptr);
	EXPECT_EQ(ftrPtr->getName(), "ChunkModeActive");
	ASSERT_EQ(ftrPtr->getType(), Mimic_Node::Type_e::BOOLEAN);
	EXPECT_EQ(ftrPtr->getValue(), true);

	EXPECT_EQ(ftrPtr->isSelector(), false);
	auto selectedFtr = ftrPtr->getSelectedFeatures();
	EXPECT_EQ(selectedFtr.size(), 0);
	auto selectingFtr = ftrPtr->getSelectingFeatures();
	EXPECT_EQ(selectingFtr.size(), 0);

	ftrPtr->setValue(false);
	EXPECT_EQ(ftrPtr->getValue(), false);

	camera->nodeMap.setValue<bool>("ChunkModeActive", true);
	EXPECT_EQ(ftrPtr->getValue(), true);
}
