/*
 * statemachine.hpp
 *
 *  Created on: Sep 14, 2023
 *      Author: Dario
 */

#ifndef INCLUDE_TACOS_STATEMACHINE_HPP_
#define INCLUDE_TACOS_STATEMACHINE_HPP_

#include <sta/config.hpp>


#ifndef STA_TACOS_NUM_STATES
#	error "Number of states wasn't defined in config.hpp"
#else


#ifndef STA_TACOS_INITIAL_STATE
#	define STA_TACOS_INITIAL_STATE 0
#endif

#define STA_TACOS_STATEMACHINE_QUEUE_LENGTH		4

// State transition happened because of
#define STA_TACOS_STATE_CHANGE_NORMAL_FLAG		( 0x1U )
#define STA_TACOS_STATE_CHANGE_FORCED_FLAG		( 0x1U << 1)
#define STA_TACOS_STATE_CHANGE_TIMEOUT			( 0x1U << 2)
#define STA_TACOS_STATE_CHANGE_STARTUP_FLAG			( 0x1U << 3)
#define STA_TACOS_STATE_CHANGE_ALL_FLAG			( 0x15U )


#include <sta/tacos/thread.hpp>
#include <sta/rtos/queue.hpp>
#include <sta/rtos/timer.hpp>
#include <sta/rtos/event.hpp>
#include <sta/event.hpp>
#include <sta/debug/assert.hpp>

#include <functional>
#include <tuple>


namespace sta
{
	namespace tacos
	{
		enum EventFlags
		{
			NORMAL 				= 0x1U,
			FORCED				= 0x1U << 1,
			TIMEOUT				= 0x1U << 2,
			STARTUP				= 0x1U << 3,
			ALL					= NORMAL | FORCED | TIMEOUT | STARTUP
		};

		struct StateTransition
		{
			uint16_t from;
			uint16_t to;
			EventFlags event;
			uint32_t lockout;
		};


		class Statemachine : public TacosThread
		{
		public:
			/**
			 * @brief The global event signaling a state change.
			 */
			static RtosEvent stateChangeEvent;

			/**
			 * @brief Getter function for the singleton instance.
			 */
			static Statemachine* instance()
			{
				static CGuard g;

				if (!_instance)
				{
					// Create the manager singleton instance.
					Statemachine::_instance = new Statemachine();
				}

				return _instance;
			}

			/**
			 * @brief Returns the statemachine's current state.
			 */
			uint16_t getCurrentState() const;

			/**
			 * @brief Request a state transition from a state to another.
			 *
			 * @param from The state which we want to leave. This is used to filter out obsolete transitions.
			 * @param to The state to transition to.
			 * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions.
			 */
			void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0);

			/**
			 * @brief Request a state transition after a given time has passed.
			 *
			 * @param from The state which we want to leave. This is used to filter out obsolete transitions.
			 * @param to The state to transition to.
			 * @param millis the number of milliseconds to wait before triggering the transition.
			 * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions.
			 */
			void requestTimedStateTransition(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0);

			void init() override;
			void func() override;

		private:
			static Statemachine * _instance;

			class CGuard
			{
			public:
			   ~CGuard()
			   {
				  if( NULL != Statemachine::_instance )
				  {
					 delete Statemachine::_instance;
					 Statemachine::_instance = NULL;
				  }
			   }
			};

			Statemachine();

			Statemachine(const Statemachine&);

			~Statemachine() {}

		private:
			/**
			 * @brief Starts the lockoutTimer for the desired duration.
			 */
			void setLockoutTimer(uint32_t millis);

		private:
			uint16_t currentState_;

			RtosTimer lockoutTimer_;
			RtosTimer failsafeTimer_;

			RtosQueue<StateTransition> queue_;
		};
	}  // namespace tacos
}  // namespace sta

#endif // STA_TACOS_NUM_STATES

#endif /* INCLUDE_TACOS_STATEMACHINE_HPP_ */
