#pragma once

/**
 * \file Time.h
 * \brief File containing the blitz::utils::AutoTimer, blitz::utils::Timer, blitz::utils::CooldownTimer and blitz::utils::DelayTimer
 * classes
 */

#include <algorithm>
#include <cstdint>
#include <functional>

namespace blitz {
namespace utils {

/**
 * \brief Get current time
 * \return Time elapsed since epoch in milliseconds
 */
std::uint64_t GetTime();

typedef std::function<void()> TimerExecFunction;

/**
 * \class AutoTimer
 * \brief Calls a function at regular period of time
 */
class AutoTimer {
  private:
	std::uint64_t m_Interval;
	TimerExecFunction m_Function;

	std::uint64_t m_LastTime = GetTime();
	std::uint64_t m_InternalTime = 0;

  public:
	/**
	 * \brief Default constructor
	 */
	AutoTimer() : m_Interval(0), m_Function(nullptr) {}

	/**
	 * \param interval Time between each function call in milliseconds
	 */
	AutoTimer(std::uint64_t interval) : m_Interval(interval), m_Function(nullptr) {}

	/**
	 * \param interval Time between each function call in milliseconds
	 * \param callback The function to call peridocally
	 */
	AutoTimer(std::uint64_t interval, TimerExecFunction callback) : m_Interval(interval), m_Function(callback) {}

	/**
	 * \brief Updates the timer
	 */
	void Update();

	/**
	 * \brief Updates the timer with the desired time step
	 * \param delta The time step in milliseconds
	 */
	void Update(std::uint64_t delta);

	/**
	 * \brief Resets the internal cooldown
	 */
	void Reset();

	/**
	 * \brief Sets a new interval
	 * \param newInterval the new interval in milliseconds
	 */
	void SetInterval(std::uint64_t newInterval) {
		m_Interval = newInterval;
	}

	/**
	 * \return The interval between each callback in milliseconds
	 */
	std::uint64_t GetInterval() const {
		return m_Interval;
	}

	/**
	 * \brief Set a new callback function
	 * \param newCallback The function to call
	 */
	void SetCallbackFunction(TimerExecFunction newCallback) {
		m_Function = newCallback;
	}

	/**
	 * \return The callback function
	 */
	TimerExecFunction GetCallbackFunction() const {
		return m_Function;
	}
};

/**
 * \class Timer
 * \brief Utililty class to trigger update at regular period of time
 */
class Timer {
  private:
	std::uint64_t m_Interval;  // in millis
	std::uint64_t m_InternalTime = 0;

  public:
	/**
	 * \brief Default constructor
	 */
	Timer() : m_Interval(0) {}

	/**
	 * \brief Construct a Timer with the desired interval
	 * \param interval The interval between each update in milliseconds
	 */
	Timer(std::uint64_t interval) : m_Interval(interval) {}

	/**
	 * \brief Updates the timer
	 * \param delta The time step in milliseconds
	 * \return true every interval milliseconds
	 */
	bool Update(std::uint64_t delta);

	/**
	 * \brief Resets the timer to its original state
	 */
	void Reset();

	/**
	 * \return The time remaing before the next Update() call will return true (in milliseconds)
	 */
	std::uint64_t GetTimeRemaining() const {
		return m_Interval - m_InternalTime;
	}

	/**
	 * \return The time elapsed since the previous Update() call returned true (in milliseconds)
	 */
	std::uint64_t GetTimeElapsed() const {
		return m_InternalTime;
	}

	/**
	 * \brief Set a new value for the interval
	 * \param newInterval the new interval to set
	 */
	void SetInterval(std::uint64_t newInterval) {
		m_Interval = newInterval;
	}

	/**
	 * \return The interval between each successful Update()
	 */
	std::uint64_t GetInterval() const {
		return m_Interval;
	}
};

/**
 * \class CooldownTimer
 * \brief Class to trigger update at regular period of time with a cooldown
 */
template <typename T>
class CooldownTimer {
  private:
	T m_Cooldown;
	T m_CooldownTime;

  public:
	/**
	 * \brief Default constructor
	 */
	CooldownTimer() : m_Cooldown(0), m_CooldownTime(0) {}

	/**
	 * \param cooldown The cooldown before Update() returns true again
	 */
	CooldownTimer(T cooldown) : m_Cooldown(0), m_CooldownTime(cooldown) {}

	/**
	 * \brief Update the timer
	 * \param delta the time step
	 */
	bool Update(T delta) {
		if (m_Cooldown > 0) {
			m_Cooldown = std::max<T>(static_cast<T>(0), static_cast<T>(m_Cooldown - delta));
		}
		return m_Cooldown == 0;
	}

	/**
	 * \brief Apply a cooldown for the timer
	 */
	void ApplyCooldown() {
		m_Cooldown = m_CooldownTime;
	}

	/**
	 * \brief Reset the cooldown. Update() will return true immediatly afterward.
	 */
	void Reset() {
		m_Cooldown = 0;
	}

	/**
	 * \brief Change the timer cooldown
	 * \param newCooldown the new cooldown
	 */
	void SetCooldown(T newCooldown) {
		m_CooldownTime = newCooldown;
	}

	/**
	 * \return The cooldown of the timer
	 */
	T GetCooldown() const {
		return m_CooldownTime;
	}
};

/**
 * \class DelayTimer
 * \brief class to trigger an update after a period of time
 */
template <typename T>
class DelayTimer {
  private:
	T m_DelayTime;
	T m_InternalTime;

  public:
	/**
	 * \brief Default constructor
	 */
	DelayTimer() : m_DelayTime(0), m_InternalTime(0) {}

	/**
	 * \param delay The delay to set
	 */
	DelayTimer(T delay) : m_DelayTime(delay), m_InternalTime(delay) {}

	/**
	 * \brief Returns true whether the delay has been passed.
	 */
	bool Update(T delta) {
		m_InternalTime = std::max<T>(static_cast<T>(0), static_cast<T>(m_InternalTime - delta));
		return m_InternalTime == 0;
	}

	/**
	 * \brief Resets the timer. Update() will immediatly return true
	 */
	void Reset() {
		m_InternalTime = m_DelayTime;
	}

	/**
	 * \brief Changes the delay of the timer
	 * \param newDelay the new delay
	 */
	void SetDelay(T newDelay) {
		m_DelayTime = newDelay;
	}

	/**
	 * \return the delay of the timer
	 */
	T GetDelay() const {
		return m_DelayTime;
	}
};

}  // namespace utils
}  // namespace blitz
