#pragma once
#include "AftrConfig.h"
#ifdef AFTR_CONFIG_USE_FMT_LIB
#include <string>//needed to compile on a Mac
#include <chrono>
#include <functional>
#include <optional>
#include <string>

namespace Aftr
{

//std::string to_string( AftrTimer::duration_type const& dur );
//See AftrTimer_test.cpp for example usages. This is a fun little timer that is lightweight and uses RAII to report results by default.

class AftrTimer
{
public:
   //Aliases for the type of time/clock used as well as for the declaration of the call back type
   using clock_type = std::chrono::steady_clock::time_point::clock;
   using duration_type = clock_type::duration;
   using call_back_func_type = std::function<void( clock_type::duration&& elapsed )>;
   //The "natural" unit of time the AftrTimer uses is a second along with its fractional part stored
   //as a double. Specifically, this type is a std::chrono::duration that internally stores a 
   //double denoting the time in seconds, including fractions of a second, since this timer was created.
   using fractional_second_type = std::chrono::duration<double, std::ratio<1, 1>>; //1:1 ratio implies seconds, double is storage

   //AftrTimer() = delete; //force default constructor to use default prefix and suffix
   ///Let's user specify a prefix the is inserted before the timing output. Helpful for a label, for example.
   AftrTimer( std::string const& prefix_before_timer_output = "", std::string const& suffix = "sec\n");
   AftrTimer( call_back_func_type cb, std::string const& prefix_before_timer_output = "", std::string const& suffix = "sec\n" );
   AftrTimer( const AftrTimer& toCopy ) = default;
   AftrTimer( AftrTimer&& toMove) noexcept;
   AftrTimer& operator=( const AftrTimer& toCopyAssign ) = default;
   AftrTimer& operator=( AftrTimer&& toMoveAssign ) noexcept;

   ///Returns the seconds + fractional seconds since this timer was created. So 12.34567 is 12 seconds and 0.34567 seconds
   fractional_second_type get_elapsed_as_seconds() const noexcept;
   ///Returns the current duration since this timer's construction
   clock_type::duration get_elapsed_as_duration() const noexcept;

   static fractional_second_type get_elapsed_duration_as_seconds( const duration_type& dur ) noexcept;
   ///Stops the timer, it will *not* invoke the callback or print timing info on destruction but the elapsed
   ///duration is available via get_elapsed_as_...() methods.
   ///Calling stop_quietly() more than once has no visible effect.
   ///After this method returns, the timer is halted -- calls to get_elapsed_...() will
   ///continue to return the time duration between creation and the point when stop_quietly() was called.
   ///The timer can no longer be started or used to make a new measurement after calling stop_quietly().
   ///Calling stop_quietly() more than once has no visible effect.
   void stop_quietly() noexcept;

   ///If not already stopped or aborted, this method will stop the timer and invoke the callback
   ///or prints timing info.
   ///After this method returns, the timer is halted -- calls to get_elapsed_...() will
   ///continue to return the time duration between creation and the point when stop() was called.
   ///The timer can no longer be started or used to make a new measurement after calling stop().
   ///Calling stop() more than once has no visible effect.
   void stop() noexcept;
   ///Either prints the time HH:MM:SS.ssss to standard out or invokes the callback passing
   ///the corresponding duration, if the callback was set during construction
   ~AftrTimer();
   static std::string to_string( const AftrTimer& t );
   static std::string to_string( const duration_type& duration,         const std::string& prefix = "", const std::string& suffix = "sec" );
   static std::string to_string( const fractional_second_type& seconds, const std::string& prefix = "", const std::string& suffix = "sec" );
   static void print( const AftrTimer& t ) noexcept;
   static void print( const fractional_second_type& seconds, const std::string& prefix = "", const std::string& suffix = "sec" ) noexcept;
   static void print( const duration_type& duration,         const std::string& prefix = "", const std::string& suffix = "sec" ) noexcept;

   call_back_func_type call_back; ///< If set, called by the destructor instead of printing the duration to std out
   std::string prefix;
   std::string suffix;
   clock_type::time_point start_time; ///< Time at which the constructor's last initializer was set

private:
   bool halted{ false };
   void shutdown( duration_type&& dur );
   std::optional<duration_type> completed_measured_duration;
};

//A wrapper around an AftrTimer that invokes the specified callback exactly once *after* countdown_time has elapsed.
class AftrCountdownTimer
{
public:
   //Returns true if the countdown has expired; false, otherwise. Calling countdown on an expired timer
   //Count down timer -- This constructor will trigger the callback after expiration_time has elapsed.
   //Since this is single threaded, the user must query periodically to see if the timer is expired. For example,
   //say the user desired the callback to be called in 1.5 seconds. The user would then invoke AftrTimer::countdown()
   //each frame (i.e., many times per second). At the frame where >= 1.5 seconds has elapsed, countdown() would invoke
   //the callback automatically. If the callback resets the timer, the timer will countdown again until it is destroyed.
   //Otherwise, the countdown is complete and the timer is in the same state as calling stop_quietly. Further 
   //calls to countdown() do nothing.
   AftrCountdownTimer( AftrTimer::duration_type countdown_time, AftrTimer::call_back_func_type expiration_func = nullptr );

   //Upon invocation, this method resets the timer to the original countdown time. If the timer is either counting
   //down OR expired, this method will do the same thing and restart the clock returning true from coundown() when
   //the time has elapsed.
   void restartCountdown();

   //Returns true exactly once upon the first poll where the elapsed time is >= the countdown_time specified in the
   //c'tor.
   bool countdown();
private:
   std::optional<AftrTimer> timer;
   AftrTimer::call_back_func_type expire_func;
   AftrTimer::duration_type countdown_time;
   

};

std::string to_string( AftrTimer::duration_type const& dur, const std::string& prefix = "", const std::string& suffix = "sec" );

}//namespace Aftr

#endif