#ifndef PROJECT_ERRORHANDLER_HPP
#define PROJECT_ERRORHANDLER_HPP

#include <stdint.h> // for the uint_8t stepID
#include <type_traits>

// Forward declaration of the class, since its header file depends on the ErrorHandler
class Message;

/**
 * A class that handles unexpected software errors, including internal errors or errors due to
 * invalid & incorrect input data.
 *
 * @todo Add auxiliary data field to errors
 */
class ErrorHandler {
private:
	/**
	 * Log the error to a logging facility. Platform-dependent.
	 */
	template <typename ErrorType>
	static void logError(const Message& message, ErrorType errorType);

	/**
	 * Log an error without a Message to a logging facility. Platform-dependent.
	 */
	template <typename ErrorType>
	static void logError(ErrorType errorType);

public:
	enum InternalErrorType {
		UnknownInternalError = 0,
		/**
		 * While writing (creating) a message, an amount of bytes was tried to be added but
		 * resulted in failure, since the message storage was not enough.
		 */
		MessageTooLarge = 1,
		/**
		 * Asked to append a number of bits larger than supported
		 */
		TooManyBitsAppend = 2,
		/**
		 * Asked to append a byte, while the previous byte was not complete
		 */
		ByteBetweenBits = 3,
		/**
		 * A string is larger than the largest allowed string
		 */
		StringTooLarge = 4,
		/**
		 * An error in the header of a packet makes it unable to be parsed
		 */
		UnacceptablePacket = 5,
		/**
		 * A date that isn't valid according to the Gregorian calendar or cannot be parsed by the
		 * TimeHelper
		 */
		InvalidDate = 6,
		/**
		 * Asked a Message type that doesn't exist
		 */
		UnknownMessageType = 7,

		/**
		 * Asked to append unnecessary spare bits
		 */
		InvalidSpareBits = 8,
		/**
		 * A function received a Message that was not of the correct type
		 */
		OtherMessageType = 9,
		/**
		 * Attempt to insert new element in a full map ST[08]
		 */
		MapFull = 10,
		/**
		 * A Message that is included within another message is too large
		 */
		NestedMessageTooLarge = 11,
		/**
		 * Request to copy packets in a time window, whose type is not recognized (ST(15)).
		 */
		InvalidTimeWindowType = 12,
		/**
		 * A request to access a non existing housekeeping structure in ST[03]
		 */
		NonExistentHousekeeping = 13,
		/**
		 * Attempt to access an invalid parameter in ST[03]
		 */
		NonExistentParameter = 14,
		/**
		 * Invalid TimeStamp parameters at creation
		 */
		InvalidTimeStampInput = 15,
	};

	/**
	 * The error code for failed acceptance reports, as specified in ECSS 6.1.4.3d
	 *
	 * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something
	 * changes.
	 */
	enum AcceptanceErrorType {
		UnknownAcceptanceError = 0,
		/**
		 * The received message does not contain enough information as specified
		 */
		MessageTooShort = 1,
		/**
		 * Asked to read a number of bits larger than supported
		 */
		TooManyBitsRead = 2,
		/**
		 * Cannot read a string, because it is larger than the largest allowed string
		 */
		StringTooShort = 4,
		/**
		 * Cannot parse a Message, because there is an error in its secondary header
		 */
		UnacceptableMessage = 5
	};

	/**
	 * The error code for failed start of execution reports, as specified in ECSS 5.3.5.2.3g
	 *
	 * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something
	 * changes.
	 */
	enum ExecutionStartErrorType {
		UnknownExecutionStartError = 0,
		/**
		 * In the Event Action Service, in the addEventActionDefinition function an attempt was
		 * made to add an event Action Definition with an eventActionDefinitionID that exists
		 */
		EventActionDefinitionIDExistsError = 1,
		/**
		 * In the Event Action Service, in the deleteEventActionDefinition function, an attempt
		 * was made to delete an event action definition that was enabled
		 */
		EventActionDeleteEnabledDefinitionError = 2,
		/**
		 * In the Event Action Service, an access attempt was made to an unknown event
		 * action definition
		 */
		EventActionUnknownEventDefinitionError = 3,
		/**
		 * EventAction refers to the service, EventActionIDefinitionID refers to the variable
		 * In the Event Action Service, an access attempt was made to an unknown eventActionDefinitionID
		 */
		EventActionUnknownEventActionDefinitionIDError = 4,
		SubServiceExecutionStartError = 5,
		InstructionExecutionStartError = 6,
		/**
		 * Attempt to change the value of a non existing parameter (ST[20])
		 */
		SetNonExistingParameter = 7,
		/**
		 * Attempt to access a non existing parameter (ST[20])
		 */
		GetNonExistingParameter = 8,
		/**
		 * Attempt to access a packet store that does not exist (ST[15])
		 */
		NonExistingPacketStore = 9,
		/**
		 * Attempt to change the start time tag of a packet store, whose open retrieval status is in progress (ST[15])
		 */
		SetPacketStoreWithOpenRetrievalInProgress = 10,
		/**
		 * Attempt to resume open retrieval of a packet store, whose by-time-range retrieval is enabled (ST[15])
		 */
		SetPacketStoreWithByTimeRangeRetrieval = 11,
		/**
		 * Attempt to access a packet with by-time range retrieval enabled (ST[15])
		 */
		GetPacketStoreWithByTimeRangeRetrieval = 12,
		/**
		 * Attempt to start the by-time-range retrieval of packet store, whose open retrieval is in progress (ST[15])
		 */
		GetPacketStoreWithOpenRetrievalInProgress = 13,
		/**
		 * Attempt to start by-time-range retrieval when its already enabled (ST[15])
		 */
		ByTimeRangeRetrievalAlreadyEnabled = 14,
		/**
		 * Attempt to create packet store, whose ID already exists (ST[15])
		 */
		AlreadyExistingPacketStore = 15,
		/**
		 * Attempt to create packet store, when the max number of packet stores is already reached (ST[15])
		 */
		MaxNumberOfPacketStoresReached = 16,
		/**
		 * Attempt to access a packet store with the storage status enabled (ST[15])
		 */
		GetPacketStoreWithStorageStatusEnabled = 17,
		/**
		 * Attempt to delete a packet whose by time range retrieval status is enabled (ST[15])
		 */
		DeletionOfPacketWithByTimeRangeRetrieval = 18,
		/**
		 * Attempt to delete a packet whose open retrieval status is in progress (ST[15])
		 */
		DeletionOfPacketWithOpenRetrievalInProgress = 19,
		/**
		 * Requested a time window where the start time is larger than the end time (ST[15])
		 */
		InvalidTimeWindow = 20,
		/**
		 * Attempt to copy a packet store to a destination packet store that is not empty (ST[15])
		 */
		DestinationPacketStoreNotEmtpy = 21,
		/**
		 * Attempt to set a reporting rate which is smaller than the parameter sampling rate.
		 * ST[04]
		 */
		InvalidReportingRateError = 22,
		/**
		 * Attempt to add definition to the struct map but its already full.(ST[19])
		 */
		EventActionDefinitionsMapIsFull = 23,
		/**
		 * Attempt to report/delete non existing housekeeping structure (ST[03])
		 */
		RequestedNonExistingStructure = 24,
		/**
		 * Attempt to create already created structure (ST[03])
		 */
		RequestedAlreadyExistingStructure = 25,
		/**
		 * Attempt to delete structure which has the periodic reporting status enabled (ST[03]) as per 6.3.3.5.2(d-2)
		 */
		RequestedDeletionOfEnabledHousekeeping = 26,
		/**
		 * Attempt to append a new parameter ID to a housekeeping structure, but the ID is already in the structure
		 * (ST[03])
		 */
		AlreadyExistingParameter = 27,
		/**
		 * Attempt to append a new parameter id to a housekeeping structure, but the periodic generation status is
		 * enabled (ST[03])
		 */
		RequestedAppendToEnabledHousekeeping = 28,
		/**
		 * Attempt to create a new housekeeping structure in Housekeeping Service, when the maximum number of
		 * housekeeping structures is already reached (ST[03])
		 */
		ExceededMaxNumberOfHousekeepingStructures = 29,
		/**
		 * Attempt to add a new simply commutated parameter in a specific housekeeping structure, but the maximum
		 * number of simply commutated parameters for this structure is already reached (ST[03])
		 */
		ExceededMaxNumberOfSimplyCommutatedParameters = 30,
		/**
		 * Attempt to set a reporting rate which is smaller than the parameter sampling rate.
		 * ST[04]
		 */
		InvalidSamplingRateError = 31,
		/**
		 * Attempt to add new statistic definition but the maximum number is already reached (ST[04])
		 */
		MaxStatisticDefinitionsReached = 32,
		/**
		 * Attempt to set the virtual channel of a packet store to a invalid value (ST[15])
		 */
		InvalidVirtualChannel = 33,
		/**
		 * Attempt to delete a packet store, whose storage status is enabled (ST[15])
		 */
		DeletionOfPacketStoreWithStorageStatusEnabled = 34,
		/**
		 * Attempt to copy packets from a packet store to another, but either no packet timestamp falls inside the
		 * specified timestamp, or more than one boolean argument were given as true in the 'copyPacketsTo' function
		 * (ST[15])
		 */
		CopyOfPacketsFailed = 35,
		/**
		 * Attempt to set a packet store size to a value that the available memory cannot handle (ST[15]).
		 */
		UnableToHandlePacketStoreSize = 36,
		/**
		 * Attempt to delete all parameter monitoring definitions but the Parameter Monitoring Function Status is
		 * enabled.
		 */
		InvalidRequestToDeleteAllParameterMonitoringDefinitions = 37,
		/**
		 * Attempt to delete one parameter monitoring definition but its Parameter Monitoring Status is
		 * enabled.
		 */
		InvalidRequestToDeleteParameterMonitoringDefinition = 38,
		/**
		 * Attempt to add a parameter that already exists to the Parameter Monitoring List.
		 */
		AddAlreadyExistingParameter = 39,
		/**
		 * Attempt to add a parameter in the Parameter Monitoring List but it's full
		 */
		ParameterMonitoringListIsFull = 40,
		/**
		 * Attempt to add or modify a limit check parameter monitoring definition, but the high limit is lower than
		 * the low limit.
		 */
		HighLimitIsLowerThanLowLimit = 41,
		/**
		 * Attempt to add or modify a delta check parameter monitoring definition, but the high threshold is lower than
		 * the low threshold.
		 */
		HighThresholdIsLowerThanLowThreshold = 42,
		/**
		 * Attempt to modify a non existent Parameter Monitoring definition.
		 */
		ModifyParameterNotInTheParameterMonitoringList = 43,
		/**
		 * Attempt to modify a parameter monitoring definition, but the instruction refers to a monitored parameter
		 * that is not the one used in that parameter monitoring definition.
		 */
		DifferentParameterMonitoringDefinitionAndMonitoredParameter = 44,
		/**
		 * Attempt to get a parameter monitoring definition that does not exist.
		 */
		GetNonExistingParameterMonitoringDefinition = 45,
		/**
		 * Request to report a non existent parameter monitoring definition.
		 */
		ReportParameterNotInTheParameterMonitoringList = 46,
		/**
		 * Attempt to add a new service type, when the addition of all service types is already enabled in the
		 * Application Process configuration (ST[14])
		 */
		AllServiceTypesAlreadyAllowed = 47,
		/**
		 * Attempt to add a new report type, when the max number of reports types allowed per service type
		 * definition in the Application Process configuration is already reached (ST[14])
		 */
		MaxReportTypesReached = 48,
		/**
		 * Attempt to add a new service type, when the max number of service types allowed per application process
		 * definition in the Application Process configuration is already reached (ST[14])
		 */
		MaxServiceTypesReached = 49,
		/**
		 * Attempt to add a report/event definition/housekeeping report type, when the specified application process
		 * ID is not controlled by the Service (ST[14])
		 */
		NotControlledApplication = 50,
		/**
		 * Parameter is requested, but the provider of the parameter value does not exist yet
		 */
		ParameterValueMissing = 51,
		/**
		 * Attempted to write to a read-only parameter
		 */
		ParameterReadOnly = 52,
		/**
		 * Attempted to read from a write-only parameter
		 */
		ParameterWriteOnly = 53,
	};

	/**
	 * The error code for failed progress of execution reports, as specified in ECSS 5.3.5.2.3g
	 *
	 * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something
	 * changes.
	 */
	enum ExecutionProgressErrorType {
		UnknownExecutionProgressError = 0,
	};

	/**
	 * The error code for failed completion of execution reports, as specified in ECSS 5.3.5.2.3g
	 *
	 * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something
	 * changes.
	 */
	enum ExecutionCompletionErrorType {
		UnknownExecutionCompletionError = 0,
		/**
		 * Checksum comparison failed
		 */
		ChecksumFailed = 1,
		/**
		 * Address of a memory is out of the defined range for the type of memory
		 */
		AddressOutOfRange = 2,
	};

	/**
	 * The error code for failed routing reports, as specified in ECSS 6.1.3.3d
	 *
	 * Note: Numbers are kept in code explicitly, so that there is no uncertainty when something
	 * changes.
	 */
	enum RoutingErrorType {
		UnknownRoutingError = 0,
	};

	/**
	 * The location where the error occurred
	 */
	enum ErrorSource {
		Internal,
		Acceptance,
		ExecutionStart,
		ExecutionProgress,
		ExecutionCompletion,
		Routing,
	};

	/**
	 * Report a failure and, if applicable, store a failure report message
	 *
	 * @tparam ErrorType The Type struct of the error; can be AcceptanceErrorType,
	 * StartExecutionErrorType,CompletionExecutionErrorType,  or RoutingErrorType.
	 * @param message The incoming message that prompted the failure
	 * @param errorCode The error's code, as defined in ErrorHandler
	 * @todo See if this needs to include InternalErrorType
	 */
	template <typename ErrorType>
	static void reportError(const Message& message, ErrorType errorCode);

	/**
	 * Report a failure about the progress of the execution of a request
	 *
	 * @note This function is different from reportError, because we need one more \p stepID
	 * to call the proper function for reporting the progress of the execution of a request
	 *
	 * @param message The incoming message that prompted the failure
	 * @param errorCode The error's code, when a failed progress of the execution of a request
	 * occurs
	 * @param stepID If the execution of a request is a long process, then we can divide
	 * the process into steps. Each step goes with its own definition, the stepID. Each value
	 * ,that the stepID is assigned, should be documented.
	 */
	static void reportProgressError(const Message& message, ExecutionProgressErrorType errorCode, uint8_t stepID);

	/**
	 * Report a failure that occurred internally, not due to a failure of a received packet.
	 *
	 * Note that these errors correspond to bugs or faults in the software, and should be treated
	 * differently. Such an error may prompt a task or software reset.
	 */
	static void reportInternalError(InternalErrorType errorCode);

	/**
	 * Make an assertion, to ensure that a runtime condition is met.
	 *
	 * Reports a failure that occurred internally, not due to a failure of a received packet.
	 *
	 * Creates an error if \p condition is false. The created error is Internal.
	 *
	 * @param condition The condition to check. Throws an error if false.
	 * @param errorCode The error code that is assigned to this error. One of the \ref ErrorHandler enum values.
	 * @return Returns \p condition, i.e. true if the assertion is successful, false if not.
	 */
	static bool assertInternal(bool condition, InternalErrorType errorCode) {
		if (not condition) {
			reportInternalError(errorCode);
		}

		return condition;
	}

	/**
	 * Make an assertion, to ensure that a runtime condition is met.
	 *
	 * Reports a failure that occurred while processing a request, in any of the process phases.
	 *
	 * Creates an error if \p condition is false. The created error corresponds to a \p message.
	 *
	 * @param condition The condition to check. Throws an error if false.
	 * @param message The message to associate with this error
	 * @param errorCode The error code that is assigned to this error. One of the \ref ErrorHandler enum values.
	 * @return Returns \p condition, i.e. true if the assertion is successful, false if not.
	 */
	template <typename ErrorType>
	static bool assertRequest(bool condition, const Message& message, ErrorType errorCode) {
		if (not condition) {
			reportError(message, errorCode);
		}

		return condition;
	}

	/**
	 * Convert a parameter given in C++ to an ErrorSource that can be easily used in comparisons.
	 * @tparam ErrorType One of the enums specified in ErrorHandler.
	 * @param error An error code of a specific type
	 * @return The corresponding ErrorSource
	 */
	template <typename ErrorType>
	inline static ErrorSource findErrorSource(ErrorType error) {
		// Static type checking
		ErrorSource source = Internal;

		if (std::is_same<ErrorType, AcceptanceErrorType>()) {
			source = Acceptance;
		}
		if (std::is_same<ErrorType, ExecutionStartErrorType>()) {
			source = ExecutionStart;
		}
		if (std::is_same<ErrorType, ExecutionProgressErrorType>()) {
			source = ExecutionProgress;
		}
		if (std::is_same<ErrorType, ExecutionCompletionErrorType>()) {
			source = ExecutionCompletion;
		}
		if (std::is_same<ErrorType, RoutingErrorType>()) {
			source = Routing;
		}

		return source;
	}
};

#endif // PROJECT_ERRORHANDLER_HPP