#pragma once
#include "data/config/AbstractConfig.h"
#include "data/config/AccelConfig.h"
#include "data/config/AccelExtConfig.h"
#include "data/config/ADC2Config.h"
#include "data/config/ADCConfig.h"
#include "data/config/CompassConfig.h"
#include "data/config/CorrelationConfig.h"
#include "data/config/DetectionConfig.h"
#include "data/config/DualAxisConfig.h"
#include "data/config/GenericSensorConfig.h"
#include "data/config/GlobalSamplingConfig.h"
#include "data/config/GPSConfigSettings.h"
#include "data/config/GyroConfig.h"
#include "data/config/HeartBeatConfig.h"
#include "data/config/IMAConfig.h"
#include "data/config/IOConfigSettings.h"
#include "data/config/LoggingConfig.h"
#include "data/config/NexoDAQSettings.h"
#include "data/config/OtherAxisCapability.h"
#include "data/config/OtherAxisConfig.h"
#include "data/config/PowerManagementSettings.h"
#include "data/config/QuadAxisConfig.h"
#include "data/config/RadioConfig.h"
#include "data/config/RTCConfig.h"
#include "data/config/SchedulerSettings.h"
#include "data/config/SensorSpecificSettings.h"
#include "data/config/SingleAxisCapability.h"
#include "data/config/SingleAxisConfig.h"
#include "data/config/StatusConfig.h"
#include "data/config/TemperatureConfig.h"
#include "data/config/TripleAxisConfig.h"
#include "data/config/VMonBoardConfig.h"
#include "data/config/TOFConfig.h"

namespace inertia
{
typedef std::shared_ptr<AbstractConfig> SensorConfigPtr; ///< Pointer to AbstractConfig.
typedef std::map<ConfigType, SensorConfigPtr> SensorConfigMap; ///< Map with configurations for a ConfigType.
enum ValueType : unsigned;


/** @brief Sensor Configuration.
 *
 *  Class used to store received configurations and to create new configurations.
 */
struct INERTIA_API InertiaConfig
{
private:
	std::shared_ptr<void> m_data;

public:
	/** Default Constructor.
	 *  Creates an empty configuration.
	 */
	InertiaConfig();

	/** Raw data Constructor.
	 *  Creates a configuration from raw binary data.
	 *  @param in input bytes
	 *  @param dbfStart start-bit in the first byte
	 */
	InertiaConfig(std::vector<uint8_t> const& in, const unsigned dbfStart = 5);

	/** Constructor with string argument.
	 *  Creates a configuration from a string.
	 *  @param argString string with arguments
	 */
	InertiaConfig(std::wstring const& argString);


	/** Get if the InertiaConfig is used to request configurations from a device.
	 *  @return true when used for requesting
	 */
	bool GetIsRequest() const;

	/** Set the InertiaConfig to request configurations from a device.
	 *  @param isRequest true to enable requesting
	 */
	void SetIsRequest(const bool isRequest);

	/** Get if the InertiaConfig is used to reset configurations of a device to its default settings.
	 *  @return true when used for setting to default settings
	 */
	bool GetIsDefault() const;

	/** Set the InertiaConfig to reset configurations of a device to its default settings.
	 *  @param isDefault true to enable setting to default settings
	 */
	void SetIsDefault(const bool isDefault);


	/** Add a configuration type that should be requested or set to its default settings.
	 *  @param config the configuration type to add
	 *  @param sensorID enable the configuration type only for the specified sensorID (relevant for AxisConfig), -1 if sensorID does not matter
	 *  @param descrType the descriptor type, e.g. configuration or capability
	 */
	void AddConfig(const ConfigType config, const int sensorID = -1, const AxisConfigDescriptorType descrType = AXIS_DESCRIPTOR_CONFIGURATION);

	/** Add a sensor configuration to this configuration that should be applied to a device.
	 *  @param config the sensor configuration to add
	 */
	void AddConfig(SensorConfigPtr const& config);


	/** Check if this InertiaConfig contains configuration items.
	 *  @return true when configurations are present
	 */
	bool HasConfigs() const;

	/** Check if this InertiaConfig contains a specific configuration type.
	 *  @param config the type of the configuration
	 *  @param sensorID the sensorID of the configuration (relevant for AxisConfig), -1 if sensorID does not matter
	 *  @param descrType the descriptor type of the configuration (relevant for AxisConfig).
	 *  @return true when the requested configuration is available
	 */
	bool HasConfig(const ConfigType config, const int sensorID = -1, const AxisConfigDescriptorType descrType = AXIS_DESCRIPTOR_CONFIGURATION) const;

	/** Get the available instances of a specific configuration type and sensorID.
	 *  @param config the type of the configuration
	 *  @param sensorID the sensorID of the configuration
	 *  @param descrType the descriptor type of the configuration
	 *  @return a vector with all available instances
	 */
	std::vector<unsigned> GetInstances(const ConfigType config, const unsigned sensorID, const AxisConfigDescriptorType descrType) const;

	/** Get a configuration.
	 *  @param config the type of configuration
	 *  @return the requested configuration, empty pointer when the configuration is not available
	 */
	SensorConfigPtr const GetConfig(const ConfigType config) const;

	/** Get the GlobalSamplingConfig configuration (::CONFIG_GLOBAL_SAMPLING).
	 *  @return the GlobalSamplingConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	GlobalSamplingConfig const& GetGlobalSamplingConfig() const;

	/** Get the RadioConfig configuration (::CONFIG_RADIO).
	 *  @return the RadioConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	RadioConfig const& GetRadioConfig() const;

	/** Get the AccelConfig configuration (::CONFIG_ACCEL).
	 *  @return the AccelConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used anymore, replaced by TripleAxisConfig with ::ACCEL_MPU9250
	 */
	AccelConfig const& GetAccelConfig() const;

	/** Get the CompassConfig configuration (::CONFIG_COMPASS).
	 *  @return the CompassConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used anymore, replaced by TripleAxisConfig with ::COMPASS_AK8963
	 */
	CompassConfig const& GetCompassConfig() const;

	/** Get the GyroConfig configuration (::CONFIG_GYRO).
	 *  @return the GyroConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used anymore, replaced by TripleAxisConfig with ::GYRO_MPU9250
	 */
	GyroConfig const& GetGyroConfig() const;

	/** Get the StatusConfig configuration (::CONFIG_STATUS).
	 *  @return the StatusConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	StatusConfig const& GetStatusConfig() const;

	/** Get the HeartBeatConfig configuration (::CONFIG_HEARTBEAT).
	 *  @return the HeartBeatConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used
	 */
	HeartBeatConfig const& GetHeartBeatConfig() const;

	/** Get the TemperatureConfig configuration (::CONFIG_STATUS).
	 *  @return the TemperatureConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used, replaced by SingleAxisConfig with ::TEMP_MPU9250
	 */
	TemperatureConfig const& GetTemperatureConfig() const;

	/** Get the CorrelationConfig configuration (::CONFIG_CORR).
	 *  @return the CorrelationConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used
	 */
	CorrelationConfig const& GetCorrelationConfig() const;

	/** Get the LoggingConfig configuration (::CONFIG_LOGGING).
	 *  @return the LoggingConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	LoggingConfig const& GetLoggingConfig() const;

	/** Get the IMAConfig configuration (::CONFIG_IMA).
	 *  @return the IMAConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	IMAConfig const& GetIMAConfig() const;

	/** Get the RTCConfig configuration (::CONFIG_RTC).
	 *  @return the RTCConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	RTCConfig const& GetRTCConfig() const;

	/** Get the ADCConfig configuration (::CONFIG_ADC).
	 *  @return the ADCConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used, replaced by ADC2Config
	 */
	ADCConfig const& GetADCConfig() const;

	/** Get the DetectionConfig configuration (::CONFIG_DETECTION).
	 *  @return the DetectionConfig
	 *  @exception std::runtime_error when configuration is not available
	 *  @deprecated not used
	 */
	DetectionConfig const& GetDetectionConfig() const;

	/** Get the ADC2Config configuration (::CONFIG_ADC2).
	 *  @return the ADC2Config
	 *  @exception std::runtime_error when configuration is not available
	 */
	ADC2Config const& GetADC2Config() const;

	/** Get the AccelConfigExt configuration (::CONFIG_ACCEL_EXT).
	 *  @return the AccelConfigExt
	 *  @deprecated not used anymore, replaced by TripleAxisConfig with ::ACCEL_MPU9250
	 */
	AccelConfigExt const& GetAccelConfigExt() const;

	/** Get the VmonBoardConfig configuration (::CONFIG_VMON_BOARD).
	 *  @return the VmonBoardConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	VmonBoardConfig const& GetVmonBoardConfig() const;

	/** Get the GenericSensorConfig of the specified type.
	 *  @param config the type of configuration (an AxisConfig)
	 *  @return the GenericSensorConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	GenericSensorConfig const& GetGenericSensorConfig(const ConfigType config) const;

	/** Get the SingleAxisConfig configuration (::CONFIG_SINGLE_AXIS).
	 *  @param sensorID a SingleAxisSensors ID
	 *  @param instance a specific instance of the sensorID, see GetInstances(), -1 to ignore and get the first available
	 *  @return the SingleAxisConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	SingleAxisConfig const& GetSingleAxisConfig(const unsigned sensorID, const int instance = -1) const;

	/** Get the DualAxisConfig configuration (::CONFIG_DUAL_AXIS).
	 *  @param sensorID a DualAxisSensors ID
	 *  @return the DualAxisConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	DualAxisConfig const& GetDualAxisConfig(const unsigned sensorID) const;

	/** Get the TripleAxisConfig configuration (::CONFIG_TRIPLE_AXIS).
	 *  @param sensorID a TripleAxisSensors ID
	 *  @return the TripleAxisConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	TripleAxisConfig const& GetTripleAxisConfig(const unsigned sensorID) const;

	/** Get the QuadAxisConfig configuration (::CONFIG_QUAD_AXIS).
	 *  @param sensorID a QuadAxisSensors ID
	 *  @return the QuadAxisConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	QuadAxisConfig const& GetQuadAxisConfig(const unsigned sensorID) const;

	/** Get the OtherAxisConfig configuration (::CONFIG_OTHER_AXIS).
	 *  @param sensorID a OtherAxisSensors ID
	 *  @return the OtherAxisConfig
	 *  @exception std::runtime_error when configuration is not available
	 */
	OtherAxisConfig const& GetOtherAxisConfig(const unsigned sensorID) const;

	/** Get the SingleAxisCapability capability (::CONFIG_SINGLE_AXIS).
	 *  @param sensorID a SingleAxisSensors ID
	 *  @param instance a specific instance of the sensorID, see GetInstances(), -1 to ignore and get the first available
	 *  @return the SingleAxisCapability
	 *  @exception std::runtime_error when capability is not available
	 */
	SingleAxisCapability const& GetSingleAxisCapability(const unsigned sensorID, const int instance = -1) const;

	/** Get the OtherAxisCapability capability (::CONFIG_SINGLE_AXIS).
	 *  @param sensorID a OtherAxisSensors ID
	 *  @return the OtherAxisCapability
	 *  @exception std::runtime_error when capability is not available
	 */
	OtherAxisCapability const& GetOtherAxisCapability(const unsigned sensorID) const;


	/** Get the first available sensorID for a specific type.
	 *  @param valueType the ValueType
	 *  @return the sensorID, (unsigned)-1 if not found
	*/
	unsigned GetSensorID(inertia::ValueType const& valueType) const;

	/** Get all sensorIDs for a specific type.
	 *  @param valueType the ValueType
	 *  @return the list of sensorIDs
	*/
	std::vector<unsigned> GetSensorIDs(inertia::ValueType const& valueType) const;


	/** Merge the given InertiaConfig into this one.
	 *  @param config the InertiaConfig to merge with
	 */
	void Merge(InertiaConfig const& config);

	/** Indicate whether the config of the specific sensorID is merged or received (relevant for AxisConfig).
	 *  @param config the type of configuration
	 *  @param sensorID the sensorID of the configuration
	 *  @return true when the config for the sensorID is received or the config has no sensorID, false when the sensorID is merged
	 */
	bool IsLastReceived(const ConfigType config, const unsigned sensorID) const;

	/** Get the bytes of the configuration.
	 *  @return a vector with the bytes
	 */
	std::vector<uint8_t> GetBytes() const;

	/** Get a map with used sampling channels and Channel information.
	 *  @return the channelMap
	 */
	const ChannelMap GetChannelMap() const;

	/** Get the lowest (closest to 1) modulo according to this configuration.
	 *  @return the lowest modulo
	 */
	unsigned GetLowestModulo() const;

	/** Get a deep copy of this InertiaConfig, making sure that shared pointers are not shared.
	 *  @return a copy of this configuration
	 */
	InertiaConfig GetDeepCopy() const;


#ifdef WITH_BOOST_PROGRAM_OPTIONS
	/** Get the possible command-line arguments for InertiaConfig.
	 *  Only available when \a WITH_BOOST_PROGRAM_OPTIONS is defined.
	 *  @return the option description
	 */
	static const po::options_description GetOptionDescription();

	/** Get the possible command-line arguments for the given configuration type.
	 *  Only available when \a WITH_BOOST_PROGRAM_OPTIONS is defined.
	 *  @param config the type of configuration
	 *  @return option description
	 */
	static const po::options_description GetOptionDescription(ConfigType const config);
#endif

	/** Get a string representation of this InertiaConfig.
	 *  The string is compatible with GetOptionDescription() and InertiaConfig(std::wstring const&).
	 *  @return the string
	 */
	const std::wstring ToString() const;

	/** Get a readable string representation of this InertiaConfig.
	 *  @param advanced include all or only basic options
	 *  @return the string
	 */
	const std::wstring ToReadableString(bool const advanced) const;

	/** Convert a configuration name to a configuration type.
	 *  @param configName the configuration name
	 *  @return the configuration type
	 */
	static ConfigType GetConfigType(std::wstring const& configName);

	/** Convert a configuration type to a configuration name.
	 *  @param type the configuration type
	 *  @return the configuration name
	 */
	static const std::wstring GetName(ConfigType const type);
};

}
