#pragma  once
#include "SensorDatumConcept.h"
#include <vector>
#include <chrono>
#include <functional>
#include "fmt/core.h"
#include <memory>
#include "AftrUtil_numeric.h"

namespace Aftr
{

//These 4 helpers enable us to use either a reference or a pointer in our Template T parameter
template<typename T>
T* ptr( T& obj ) { return &obj; } //turn reference into pointer!

template<typename T>
T* ptr( T* obj ) { return obj; } //obj is already pointer, return it

template<typename T>
T* ptr( std::unique_ptr<T>& obj ) { return obj.get(); } //obj is already pointer, return it

template<typename T>
T* ptr( std::shared_ptr<T>& obj ) { return obj.get(); } //obj is already pointer, return it

template< SensorDatumConcept T > /* , typename C > could template the clock type as well */
class SensorDatumTraversal
{
public:
   using value_type = T;
   ///static_assert( //(std::is_base_of<AftrSensorData, typename T>::value) || 
   ///               (std::is_base_of<Aftr::AftrLogDataRYO, typename T >::value), 
   ///                "ERROR: T::value_type is not derived from the Aftr::AftrSensorData base class...\n" );
   ///typedef typename std::enable_if<std::is_base_of<AftrSensorData, typename T>::value, T>::type base;
   ///typedef typename std::enable_if<std::is_base_of<AftrSensorData, typename T>::value, T>::type::value_type value_type;

   SensorDatumTraversal();
   SensorDatumTraversal( std::vector<T>&& dat );
   SensorDatumTraversal& operator=( SensorDatumTraversal&& toMove ) noexcept;
   SensorDatumTraversal( const SensorDatumTraversal& toCopy ) = delete;
   SensorDatumTraversal& operator=( const SensorDatumTraversal& toAssign ) = delete;
   ~SensorDatumTraversal();

   /// Initializes this data set for playback starting at index zero. When execution enters this method, the system clock
   /// is used to mark the "stopwatch" used to march this data forward. The "stopwatch" progresses using wall time.
   std::chrono::utc_clock::time_point initPlayback();

   /// Initializes this data set for playback starting at index zero. This overloaded method sets the "stopwatch"
   /// to the in passed point in time. The "stopwatch" progresses using wall time.
   /// This overload let's one initialize many Data sets with an identically synchronized "stopwatch" so
   /// all the data is traversed along the same wall time.
   std::chrono::utc_clock::time_point  initPlayback( std::chrono::utc_clock::time_point const& initializationTimePoint );

   /// Returns the closest data point to wall time since last call to initPlayback.
   /// Both getNextDatum and getNextNearestDatum internally mutate a reference to the most recently
   /// returned datum. Therefore calling one and then the other are not associative.
   /// Typically, one would call only getNextDatum for an entire run (to replay *every* point) OR
   /// one would call only getNextNearestDatum for an entire run to replay at wall clock time.
   const T& getNextNearestDatum( bool interpolate = false );

   /// Returns the next adjacent data point immediately next to the last replayed data point.
   /// This getter lets you replay every single data point and is *not* tied to wall clock time.
   /// Both getNextDatum and getNextNearestDatum internally mutate a reference to the most recently
   /// returned datum. Therefore calling one and then the other are not associative.
   /// Typically, one would call only getNextDatum for an entire run (to replay *every* point) OR
   /// one would call only getNextNearestDatum for an entire run to replay at wall clock time.
   const T& getNextDatum() noexcept;

   void setTimeMultiplier( double multiplier );
   double getTimeMultiplier();

   const std::vector<T>& getData() { return this->dat; }

   double getPlaybackLocation() const;
   size_t getPlaybackLocationIdx() const;

   void setPlaybackLocation( double parametricProgressThruData, std::chrono::utc_clock::time_point const& timeNow );
   void setPlaybackLocationIdx( size_t datumIndex, std::chrono::utc_clock::time_point const& timeNow );

   //Finds first time series datum, d, where pred is true. Returns that [d,d+length) as new SensorDatumTraversal<T>.
   SensorDatumTraversal<T> static trimDataSetTo( std::function<bool( const T& )> pred, int length, SensorDatumTraversal<T>&& dat );

private:

   std::vector< T > dat; ///< Log Data stored in this instance
   
   /// Time point at which initPlayback() is invoked.
   std::chrono::utc_clock::time_point startTime;
   
   /// Timepoint at dat[0].
   std::chrono::utc_clock::time_point timeAtFirstDatum;

   size_t curIdx = 0; ///< Last index retrieved via getDatum

   /// Determines how much faster or slower than "wall time" this data is traversed (played back).
   /// A value of 1.0 will play back the data at the same rate as timestamps unfold in the data
   /// A value of 2.0 will play back the data at twice normal speed
   /// A value of -1.0 will "rewind" the data at normal speed.
   double timeMultiplier = 1.0;

};

} //namespace Aftr

#include "SensorDatumTraversal.cpptemplate.h"
