#pragma once

#include <chrono>
#include <functional>

class Timer
{
  public:
	/// Represents the frequency of the timer, in Hz.
	using freq_hz_t = std::chrono::duration<uint64_t, std::ratio<1>>;

	/// Represents the period of the timer, in microseconds.
	using period_t = std::chrono::duration<uint64_t, std::micro>;

	/// Alias for timer callback functions.
	using cb_t = std::function<void(void)>;

	/// Configuration for the timer.
	enum class Config : uint8_t
	{
		/// The timer will run once, and then will remain in a stopped State
		/// until reconfigured.
		oneshot = 0,
		/// When the timer expires, the existing Config will be reloaded and
		/// it will be rearmed.
		periodic
	};

	/// Possible timer States.
	enum class State : uint8_t
	{
		/// The timer has been manually stopped or is not yet configured.
		stopped = 0,
		/// The timer is armed and running.
		armed,
	};

  protected:
	Timer() = default;
	virtual ~Timer() = default;

  public:
	/// Start the timer, which performs any initialization, configuration, and enabling-related
	/// activities.
	virtual void start() noexcept = 0;

	/// Stop the timer, which disables the timer, de-initializes, and otherwise frees up resources.
	virtual void stop() noexcept = 0;

	/** Get the current timer period.
	 *
	 * @returns the currently configured timer period.
	 */
	[[nodiscard]] period_t period() const noexcept
	{
		return period_;
	}

	/** Set the timer period.
	 *
	 * If the timer is currently running, this call has no effect. Use restart() instead.
	 *
	 * @param period the desired timer period.
	 * @returns the currently configured timer period.
	 */
	period_t period(period_t period) noexcept
	{
		period_ = period;

		return period_;
	}

	/** Register a timer callback.
	 *
	 * The timer callback function is called whenever the timer expires.
	 *
	 * Derived classes must implement this function. Derived classes can choose to support either
	 * a single callback or multiple callbacks.
	 *
	 * @param cb A callback to invoke when the timer expires.
	 */
	virtual void registerCallback(const cb_t& cb) noexcept = 0;

	/** Register a timer callback.
	 *
	 * The timer callback function is called whenever the timer expires.
	 *
	 * Derived classes must implement this function. Derived classes can choose to support either
	 * a single callback or multiple callbacks.
	 *
	 * @param cb A callback to invoke when the timer expires.
	 */
	virtual void registerCallback(cb_t&& cb) noexcept = 0;

	/** Restart the timer with a specific period.
	 *
	 * @param period The desired timer period to reset the timer to use. Accepts
	 * 	a std::chrono value.
	 */
	virtual void restart(const period_t period) noexcept
	{
		stop();

		period_ = period;

		start();
	}

	/** Restart the timer with a specific period.
	 *
	 * @param period The desired timer period to reset the timer to use.
	 *	Accepts an integral count representing the period in microseconds.
	 */
	void restart(const period_t::rep period) noexcept
	{
		restart(period_t(period));
	}

	/// Restart the timer with the currently configured period.
	void restart() noexcept
	{
		restart(period_);
	}

	/** Check the State of the timer.
	 *
	 * @returns the current timer State.
	 */
	[[nodiscard]] virtual auto state() const noexcept -> State
	{
		return state_;
	}

	/** Check the Config of the timer.
	 *
	 * @returns the current timer Config.
	 */
	[[nodiscard]] virtual auto config() const noexcept -> Config
	{
		return config_;
	}

	/** Set the timer Config.
	 *
	 * @param c The desired timer Config (periodic or oneshot).
	 * @returns the current timer Config.
	 */
	virtual auto config(Config c) noexcept -> Config
	{
		config_ = c;
		return c;
	}

	/** Read the current timer count.
	 *
	 * @returns the current count of the timer
	 */
	[[nodiscard]] virtual period_t count() const noexcept = 0;

  protected:
	/// The configured timer period.
	period_t period_{0};

	/// The current State of the timer.
	State state_ = State::stopped;

	/// The timer Config.
	Config config_ = Config::oneshot;
};
