#pragma once
#include "accessor_factory.hpp"

#include <exception>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

class nanolib_exception : public std::exception {
public:
	nanolib_exception(std::string msg) : message(msg) {
	}

	virtual char const *what() const noexcept {
		return message.c_str();
	}

private:
	std::string message;
};

class NanoLibHelper {
public:
	NanoLibHelper();
	virtual ~NanoLibHelper();

	/**
	 * @brief Creates and stores the nanolib accessor
	 *
	 * Note: call this function before calling another function
	 */
	void setup();

	/**
	 * @brief Get a list of available bus hardware
	 *
	 * @return std::vector<nlc::BusHardwareId>
	 */
	std::vector<nlc::BusHardwareId> getBusHardware() const;

	/**
	 * @brief Create bus hardware options object
	 *
	 * @return nlc::BusHardwareOptions A set of options for opening the bus hardware
	 */
	nlc::BusHardwareOptions createBusHardwareOptions(const nlc::BusHardwareId &busHardwareId) const;

	/**
	 * @brief Opens the bus hardware with given id and options
	 *
	 * @param busHwId The id of the bus hardware taken from NanoLibHelper.getBusHardware()
	 * @param busHwOptions The hardware options taken from
	 * NanoLibHelper.createBusHardwareOptions()
	 */
	void openBusHardware(nlc::BusHardwareId const &busHwId,
						 nlc::BusHardwareOptions const &busHwOptions) const;

	/**
	 * @brief Closes the bus hardware (access no longer possible after that)
	 *
	 * Note: the call of the function is optional because the nanolib will cleanup the
	 * bus hardware itself on closing.
	 *
	 * @param busHwId The bus hardware id to close
	 */
	void closeBusHardware(nlc::BusHardwareId const &busHwId) const;

	/**
	 * @brief Scans bus and returns all found device ids.
	 *
	 * CAUTION: open bus hardware first with NanoLibHelper.openBusHardware()
	 *
	 * Note: this functionality is not available on all bus hardwares. It is assumed that
	 * this example runs with CANopen where the scan is possible.
	 *
	 * @param busHwId The bus hardware to scan
	 * @return std::vector<nlc::DeviceId> Vector with found devices
	 */
	std::vector<nlc::DeviceId> scanBus(nlc::BusHardwareId const &busHwId) const;

	/**
	 * @brief Create device instance and get a handle to it
	 *
	 * @param deviceId The DeviceId of the device
	 * @return nlc::DeviceHandle
	 */
	nlc::DeviceHandle createDevice(nlc::DeviceId const &deviceId) const;

	/**
	 * @brief Connects to given device id
	 *
	 * @param deviceId The device id to connect to
	 */
	void connectDevice(nlc::DeviceHandle const &deviceId) const;

	/**
	 * @brief Disconnects given device
	 *
	 * Note: the call of the function is optional because the nanolib will cleanup the
	 * devices on bus itself on closing.
	 *
	 * @param deviceId
	 */
	void disconnectDevice(nlc::DeviceHandle const &deviceId) const;

	/**
	 * @brief Reads out an integer of given device
	 *
	 * Note: the interpretation of the data type is up to the user. Signed integer
	 * are interpreted as unsigned integer.
	 *
	 * @param deviceId The id of the device to read from
	 * @param odIndex The index and sub-index of the object dictionary to read from
	 *
	 * @return int
	 */
	int64_t readInteger(nlc::DeviceHandle const &deviceId, nlc::OdIndex const &odIndex) const;

	/**
	 * @brief Writes given value to the device
	 *
	 * @param deviceId The id of the device to write to
	 * @param value The value to write to the device
	 * @param odIndex The index and sub-index of the object dictionary to write to
	 * @param bitLength The bit length of the object to write to, either 8, 16 to 32
	 * 		(see manual for all the bit lengths of all objects)
	 */
	void writeInteger(nlc::DeviceHandle const &deviceId, int value, nlc::OdIndex const &odIndex,
					  int bitLength) const;

	/**
	 * @brief Reads out a od object array
	 *
	 * Note: the interpretation of the data type is up to the user. Signed integer
	 * are interpreted as unsigned integer.
	 *
	 * @param deviceId The id of the device to read from
	 * @param odIndex The index and sub-index of the object dictionary to read from
	 *
	 * @return std::vector<std::uint32_t>
	 */
	std::vector<std::int64_t> readArray(nlc::DeviceHandle const &deviceId,
										nlc::OdIndex const &odIndex) const;

	/**
	 * @brief Reads out string from device
	 *
	 * @param deviceId The id of the device to read from
	 * @param odIndex The index and sub-index of the object dictionary to read from
	 *
	 * @return std::string
	 */
	std::string readString(nlc::DeviceHandle const &deviceId, nlc::OdIndex const &odIndex) const;

	/**
	 * @brief Set the logging level
	 *
	 * @param logLevel
	 */
	void setLoggingLevel(nlc::LogLevel logLevel);

private:
	nlc::NanoLibAccessor *nanolibAccessor;

	/**
	 * @brief Helper function for creating an error message from given objects
	 *
	 * @param function The name of the function the error ocurred
	 * @param deviceId The id of the device to read from
	 * @param odIndex The index and sub-index of the object dictionary
	 * @param resultError The error text of the result
	 *
	 * @return std::string
	 */
	std::string createErrorMessage(std::string const &function, nlc::DeviceId const &deviceId,
								   nlc::OdIndex const &odIndex,
								   std::string const &resultError) const;
};