#pragma once
#include <string>
#include <sstream>
#include <chrono>
#include <format>
#include <fmt/std.h>
#include <fmt/format.h>
#include <fmt/core.h>
#include <fmt/chrono.h>
#include <fmt/ostream.h>
#include "../external/dateChronoRepo/chrono_io.h"
#include "../external/dateChronoRepo/date.h"
#include "../external/dateChronoRepo/tz.h"
#include "NetMessengerStreamBuffer.h"

//For usage examples, see AftrUtil_time_test.cpp, but pretty much do this:

//std::string str_utc = Aftr::toStringUTC( std::chrono::utc_clock::now() );
//auto utc_time_point = Aftr::fromStringUTC<std::chrono::utc_clock>( str_utc );



//To Run GTEST vs std::chrono and std::format, set this to directive to 0
//I had to put this in here because GCC 13.1 doesn't support std::format on a UTC chrono yet
//and sadly, lib fmt doesn't match the expected standard's output for std::format. So
//I am using Howard Hinnant's date library to do the formatted which matches the ISO SPEC
#define _SLN_ONLY_USE_HOWARD_HINNANTS_DATE_LIB_FOR_UTC_PARSE_AND_FORMAT 1

namespace Aftr
{
   //Common helpful functions to using std::chrono, outputting it to a string and vice-versa.

   //When passed as the format string a time_point will be converted to a string of the format:
   //"2023.Apr.18_15.01.14.2104535.UTC"  // This is also a valid filename, too!
   constexpr char const* DATE_TIME_FMT_STR{            "{0:%Y.%b.%d_%H.%M.%S.%Z}" }; //Must use this in std::format
   constexpr char const* DATE_TIME_FMT_STR_FROM_STREAM{   "%Y.%b.%d_%H.%M.%S.%Z" };  //use in std::chrono::from_stream


   template< typename CLOCK_TYPE >
   std::string toStringUTC_impl_date_hinnant( std::chrono::time_point<CLOCK_TYPE> tp )
   {
      //SLN written using Howard Hinnant's Date library since GCC 13.1 still doesn't support std::format and fmt::format doesn't match std::format!
      auto sys_tp = std::chrono::clock_cast<std::chrono::system_clock>(tp);
      auto str = date::format( DATE_TIME_FMT_STR_FROM_STREAM, sys_tp ); //date::format needs a sys_clock as input
      return str;
   }

   /// Sample usage:
   /// auto myStr = Aftr::toString( std::chrono::system_clock::now() );
   /// now, myStr == ""2023.Apr.18_15.01.14.2104535.UTC""
   /// This simplies calls return std::format( DATE_TIME_FMT_STR, tp );
   template< typename CLOCK_TYPE >
   std::string toStringUTC( std::chrono::time_point<CLOCK_TYPE> tp )
   {
#ifdef _SLN_ONLY_USE_HOWARD_HINNANTS_DATE_LIB_FOR_UTC_PARSE_AND_FORMAT
      return toStringUTC_impl_date_hinnant<CLOCK_TYPE>( tp );
#else
      return std::format( DATE_TIME_FMT_STR, tp );
#endif   
   }


   //Expects time is in format specified by: DATE_TIME_FMT_STR_FROM_STREAM
   //ie, "2023.Apr.18_15.01.14.2104535.UTC"
   //Example Usage:
   //std::chrono::utc_clock time = Aftr::fromStringUTC<std::chrono::utc_clock>( utc_time_as_str );
   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC( std::string const& str_with_date_time )
   {
#ifdef _SLN_ONLY_USE_HOWARD_HINNANTS_DATE_LIB_FOR_UTC_PARSE_AND_FORMAT
      return fromStringUTC_impl_date_hinnant<CLOCK_TYPE>(str_with_date_time);
#else 
      std::chrono::time_point<CLOCK_TYPE> tPoint;
      std::istringstream ss{ str_with_date_time };
      std::chrono::from_stream( ss, Aftr::DATE_TIME_FMT_STR_FROM_STREAM, tPoint );
      return tPoint;
#endif   
   }

   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC_impl_date_hinnant( std::string const& str_with_date_time )
   {
      //SLN written using Howard Hinnant's Date library since GCC 13.1 still doesn't support std::format and fmt::format doesn't match std::format!
      std::chrono::time_point< std::chrono::system_clock > tp;
      std::istringstream in{ str_with_date_time };
      date::from_stream( in, DATE_TIME_FMT_STR_FROM_STREAM, tp );
      auto tPoint = std::chrono::clock_cast<CLOCK_TYPE>(tp);
      return tPoint;
   }

   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC_impl_date_hinnant( std::istream& is )
   {
      //SLN written using Howard Hinnant's Date library since GCC 13.1 still doesn't support std::format and fmt::format doesn't match std::format!
      std::string s; is >> s;          //sln for some reason, using from_stream on istream only works every other time, but istringstream works everytime.
      std::istringstream ss{ s };   //sln for //https://github.com/microsoft/STL/issues/1952
      std::chrono::time_point< std::chrono::system_clock > tp;
      date::from_stream( ss, Aftr::DATE_TIME_FMT_STR_FROM_STREAM, tp );
      auto tPoint = std::chrono::clock_cast<CLOCK_TYPE>(tp);
      return tPoint;
   }

   //expects time is in format specified by: DATE_TIME_FMT_STR_FROM_STREAM
   //ie, "2023.Apr.18_15.01.14.2104535.UTC"
   //Example usage:
   //std::string utc_time_as_str;
   //is >> utc_time_as_str; //assume is is an input stream containing the time as a string with no spaces "2023.Apr.18_15.01.14.2104535.UTC"
   //datum.time = Aftr::fromStringUTC<std::chrono::utc_clock>( utc_time_as_str );
   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC( std::istream& is )
   {
#ifdef _SLN_ONLY_USE_HOWARD_HINNANTS_DATE_LIB_FOR_UTC_PARSE_AND_FORMAT
      return fromStringUTC_impl_date_hinnant<CLOCK_TYPE>( is );
#else 
      std::chrono::time_point<CLOCK_TYPE> tPoint;
      std::string s; is >> s;          //sln for some reason, using from_stream on istream only works every other time, but istringstream works everytime.
      std::istringstream ss{ s };   //sln for //https://github.com/microsoft/STL/issues/1952
      std::chrono::from_stream( ss, Aftr::DATE_TIME_FMT_STR_FROM_STREAM, tPoint );
      return tPoint;
#endif
   }

   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC_impl_date_hinnant( Aftr::NetMessengerStreamBuffer& is )
   {
      //SLN written using Howard Hinnant's Date library since GCC 13.1 still doesn't support std::format and fmt::format doesn't match std::format!
      std::string s; is >> s;
      std::istringstream ss{ s };
      std::chrono::time_point<std::chrono::system_clock> tp;
      date::from_stream( ss, Aftr::DATE_TIME_FMT_STR_FROM_STREAM, tp );
      auto tPoint = std::chrono::clock_cast<CLOCK_TYPE>(tp);
      return tPoint;
   }

   template< typename CLOCK_TYPE >
   std::chrono::time_point<CLOCK_TYPE> fromStringUTC( Aftr::NetMessengerStreamBuffer& is )
   {
#ifdef _SLN_ONLY_USE_HOWARD_HINNANTS_DATE_LIB_FOR_UTC_PARSE_AND_FORMAT
      return fromStringUTC_impl_date_hinnant<CLOCK_TYPE>( is );
#else
      std::chrono::time_point<CLOCK_TYPE> tPoint;
      std::string s; is >> s;      
      std::istringstream ss{ s };
      std::chrono::from_stream( ss, Aftr::DATE_TIME_FMT_STR_FROM_STREAM, tPoint );
      return tPoint;
#endif
   }

  
   


} //namespace Aftr