#ifndef ECSS_SERVICES_NOTIFYPARAMETER_HPP
#define ECSS_SERVICES_NOTIFYPARAMETER_HPP

#include <etl/optional.h>
#include <functional>
#include "Parameter.hpp"

/**
 * A Notifying parameter will call a function whenever its value is written to.
 *
 * This is useful for updating the state of things when a parameter is changed,
 * for example to disable/enable peripherals, to make configuration changes etc.
 *
 * @warning Calling NotifyParameter::setValue will *not* call the notifier
 * function. You should use setValueLoudly for this purpose instead.
 *
 * @tparam DataType The data type of the parameter's value
 */
template <typename DataType>
class NotifyParameter : public Parameter<DataType> {
public:
	using Notifier = std::function<void(const DataType&)>;
	using Parent = Parameter<DataType>;

	/**
	 * Constructor without a notifier function. Nothing will then happen when the parameter is updated.
	 */
	explicit NotifyParameter(DataType initialValue) : Parent(initialValue) {}

	/**
	 * Constructor with a default notifier function.
	 */
	NotifyParameter(DataType initialValue, const Notifier& notifier) : Parent(initialValue), notifier(notifier) {}

	/**
	 * Same as Parameter::setValue(), but also calls the NotifyParameter::notifier function, if it
	 * exists.
	 */
	inline void setValueLoudly(DataType value) {
		Parent::setValue(value);

		if (notifier) {
			(*notifier)(Parent::currentValue);
		}
	}

	/**
	 * Call the notifier if it exists, without updating the value
	 */
	inline void notify() {
		if (notifier) {
			(*notifier)(Parent::currentValue);
		}
	}

	inline void setValueFromMessage(Message& message) override {
		Parent::setValueFromMessage(message);

		if (notifier) {
			(*notifier)(Parent::currentValue);
		}
	}

	/**
	 * Set the notifier function, to be called whenever the value of this parameter is updated.
	 *
	 * @note This function will be called even when a _parameter update_ command is received, but the
	 * new value is the same as the previous one. This is done so that there is an option to repair
	 * systems with a weird or unknown state.
	 * @param call Whether to also call the notifier function immediately, to ensure that a change is
	 * made.
	 */
	void setNotifier(const Notifier& _notifier, bool call=true) {
		notifier = _notifier;

		if (call) {
			_notifier(Parent::currentValue);
		}
	}

	/**
	 * Unset the notifier function, so that nothing is called when the value of this function is updated.
	 */
	void unsetNotifier() {
		notifier.reset();
	}

private:
	etl::optional<Notifier> notifier;
};


#endif //ECSS_SERVICES_NOTIFYPARAMETER_HPP