#include "nanolib_helper.hpp"

#include "nano_lib_hw_strings.hpp"

NanoLibHelper::NanoLibHelper() : nanolibAccessor(nullptr) {
}

NanoLibHelper::~NanoLibHelper() {
	// Note: the deletion of the pointer is up to the user!
	if (nanolibAccessor != nullptr) {
		delete nanolibAccessor;
	}
}

void NanoLibHelper::setup() {
	// before accessing the nanolib, the pointer to the accessor class
	// needs to be created and stored somewhere
	nanolibAccessor = getNanoLibAccessor();
}

std::vector<nlc::BusHardwareId> NanoLibHelper::getBusHardware() const {
	nlc::ResultBusHwIds result = nanolibAccessor->listAvailableBusHardware();

	if (result.hasError()) {
		std::string errorMsg = "Error: listAvailableBusHardware() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}

	return result.getResult();
}

nlc::BusHardwareOptions
NanoLibHelper::createBusHardwareOptions(const nlc::BusHardwareId &busHardwareId) const {
	// create new bus hardware options
	nlc::BusHardwareOptions busHwOptions;

	// now add all options necessary for opening the bus hardware
	if (busHardwareId.getProtocol() == nlc::BUS_HARDWARE_ID_PROTOCOL_CANOPEN) {
		// in case of CAN bus it is the baud rate
		busHwOptions.addOption(nlc::busHwOptionsDefaults.canBus.BAUD_RATE_OPTIONS_NAME,
							   nlc::busHwOptionsDefaults.canBus.baudRate.BAUD_RATE_1000K);

		if (busHardwareId.getBusHardware() == nlc::BUS_HARDWARE_ID_IXXAT) {
			// in case of HMS IXXAT we need also bus number
			busHwOptions.addOption(
				nlc::busHwOptionsDefaults.canBus.ixxat.ADAPTER_BUS_NUMBER_OPTIONS_NAME,
				nlc::busHwOptionsDefaults.canBus.ixxat.adapterBusNumber.BUS_NUMBER_0_DEFAULT);
		}
	} else if (busHardwareId.getProtocol() == nlc::BUS_HARDWARE_ID_PROTOCOL_MODBUS_RTU) {
		// in case of Modbus RTU it is the serial baud rate
		busHwOptions.addOption(nlc::busHwOptionsDefaults.serial.BAUD_RATE_OPTIONS_NAME,
							   nlc::busHwOptionsDefaults.serial.baudRate.BAUD_RATE_19200);
		// and serial parity
		busHwOptions.addOption(nlc::busHwOptionsDefaults.serial.PARITY_OPTIONS_NAME,
							   nlc::busHwOptionsDefaults.serial.parity.EVEN);
	} else if ((busHardwareId.getProtocol() == nlc::BUS_HARDWARE_ID_PROTOCOL_MODBUS_VCP)
			   || (busHardwareId.getProtocol() == nlc::BUS_HARDWARE_ID_PROTOCOL_MODBUS_TCP)) {
		// in case of Modbus VCP/TCP, nothing is needed
	} else {
		std::string errorMsg = "Error: unknown protocol";
		throw nanolib_exception(errorMsg);
	}

	return busHwOptions;
}

void NanoLibHelper::openBusHardware(nlc::BusHardwareId const &busHwId,
									nlc::BusHardwareOptions const &busHwOptions) const {
	nlc::ResultVoid result = nanolibAccessor->openBusHardwareWithProtocol(busHwId, busHwOptions);

	if (result.hasError()) {
		std::string errorMsg = "Error: openBusHardwareWithProtocol() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}
}

void NanoLibHelper::closeBusHardware(nlc::BusHardwareId const &busHwId) const {
	nlc::ResultVoid result = nanolibAccessor->closeBusHardware(busHwId);

	if (result.hasError()) {
		std::string errorMsg = "Error: closeBusHardware() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}
}

class ScanBusCallback : public nlc::NlcScanBusCallback {
	nlc::ResultVoid callback(nlc::BusScanInfo info, std::vector<nlc::DeviceId> const &devicesFound,
							 int32_t data) {
		(void)devicesFound;
		switch (info) {
		case nlc::BusScanInfo::Start:
			std::cout << "Scan started." << std::endl;
			break;

		case nlc::BusScanInfo::Progress:
			if ((data & 1) == 0) // data holds scan progress
			{
				std::cout << ".";
			}
			break;

		case nlc::BusScanInfo::Finished:
			std::cout << std::endl << "Scan finished." << std::endl;
			break;

		default:
			break;
		}

		return nlc::ResultVoid();
	}
};

std::vector<nlc::DeviceId> NanoLibHelper::scanBus(nlc::BusHardwareId const &busHwId) const {
	auto callbackScanBus = new ScanBusCallback();
	nlc::ResultDeviceIds result = nanolibAccessor->scanDevices(busHwId, callbackScanBus);

	if (result.hasError()) {
		std::string errorMsg = "Error: scanDevices() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}

	return result.getResult();
}

nlc::DeviceHandle NanoLibHelper::createDevice(nlc::DeviceId const &deviceId) const {
	auto result = nanolibAccessor->addDevice(deviceId);
	if (result.hasError()) {
		std::string errorMsg = "Error: createDevice() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}
	return result.getResult();
}

void NanoLibHelper::connectDevice(nlc::DeviceHandle const &deviceId) const {
	nlc::ResultVoid result = nanolibAccessor->connectDevice(deviceId);

	if (result.hasError()) {
		std::string errorMsg = "Error: connectDevice() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}
}

void NanoLibHelper::disconnectDevice(nlc::DeviceHandle const &deviceId) const {
	nlc::ResultVoid result = nanolibAccessor->disconnectDevice(deviceId);
	if (result.hasError()) {
		std::string errorMsg = "Error: disconnectDevice() - " + result.getError();
		throw nanolib_exception(errorMsg);
	}
}

int64_t NanoLibHelper::readInteger(nlc::DeviceHandle const &deviceId,
								   nlc::OdIndex const &odIndex) const {
	nlc::ResultInt result = nanolibAccessor->readNumber(deviceId, odIndex);

	if (result.hasError()) {
		std::string errorMsg = "Error: cannot read integer from node index";
		throw nanolib_exception(errorMsg);
	}

	return result.getResult();
}

void NanoLibHelper::writeInteger(nlc::DeviceHandle const &deviceId, int value,
								 nlc::OdIndex const &odIndex, int bitLength) const {
	nlc::ResultVoid result = nanolibAccessor->writeNumber(deviceId, value, odIndex, bitLength);

	if (result.hasError()) {
		std::string errorMsg = "Error: cannot write integer to node index";
		throw nanolib_exception(errorMsg);
	}
}

std::vector<std::int64_t> NanoLibHelper::readArray(nlc::DeviceHandle const &deviceId,
												   nlc::OdIndex const &odIndex) const {
	nlc::ResultArrayInt result = nanolibAccessor->readNumberArray(deviceId, odIndex.getIndex());

	if (result.hasError()) {
		std::string errorMsg = "Error: cannot read array from node index";
		throw errorMsg;
	}

	return result.getResult();
}

std::string NanoLibHelper::readString(nlc::DeviceHandle const &deviceId,
									  nlc::OdIndex const &odIndex) const {
	nlc::ResultString result = nanolibAccessor->readString(deviceId, odIndex);

	if (result.hasError()) {
		std::string errorMsg = "Error: cannot read string from node index";
		throw nanolib_exception(errorMsg);
	}

	return result.getResult();
}

void NanoLibHelper::setLoggingLevel(nlc::LogLevel logLevel) {
	if (nanolibAccessor == nullptr) {
		std::string errorMsg = "Error: NanoLibHelper::setup() is required";
		throw errorMsg;
	}

	nanolibAccessor->setLoggingLevel(logLevel);
}

std::string NanoLibHelper::createErrorMessage(std::string const &function,
											  nlc::DeviceId const &deviceId,
											  nlc::OdIndex const &odIndex,
											  std::string const &resultError) const {
	std::stringstream strStr;
	strStr << "Running function \"" << function << "\" on device " << deviceId.toString();
	strStr << " at od index " << odIndex.toString();
	strStr << " resulted in an error: " << resultError;

	return strStr.str();
}