template< Aftr::SensorDatumConcept T >
Aftr::SensorDatumTraversal<T>::SensorDatumTraversal()
{
}

template< Aftr::SensorDatumConcept T >
Aftr::SensorDatumTraversal<T>::SensorDatumTraversal( std::vector<T>&& dat ) : dat( std::move(dat) )
{
}

template< Aftr::SensorDatumConcept T >
Aftr::SensorDatumTraversal<T>& Aftr::SensorDatumTraversal<T>::operator=( SensorDatumTraversal&& toMove ) noexcept
{
   this->dat = std::move( toMove.dat );
   this->startTime = toMove.startTime;
   ////this->timeDurationAtFirstDatum = toMove.timeDurationAtFirstDatum;
   this->timeAtFirstDatum = toMove.timeAtFirstDatum;
   this->curIdx = toMove.curIdx;
   this->timeMultiplier = toMove.timeMultiplier;
   return *this;
}

template< Aftr::SensorDatumConcept T >
Aftr::SensorDatumTraversal<T>::~SensorDatumTraversal()
{
}

template< Aftr::SensorDatumConcept T >
std::chrono::utc_clock::time_point Aftr::SensorDatumTraversal<T>::initPlayback()
{
   return this->initPlayback( std::chrono::utc_clock::now() );   
}

template< Aftr::SensorDatumConcept T >
std::chrono::utc_clock::time_point Aftr::SensorDatumTraversal<T>::initPlayback(
   std::chrono::utc_clock::time_point const& initializationTimePoint )
{
   ////this->timeDurationAtFirstDatum = std::chrono::duration< std::chrono::system_clock::rep, std::chrono::system_clock::period >( 0 );
   this->startTime = initializationTimePoint;
   this->curIdx = 0;

   //auto rawDatumTimeDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx] )->getDuration() ).count();
   if( this->dat.size() > 0 )
      this->timeAtFirstDatum = this->dat[0].getTime();

   return startTime;
}

template< Aftr::SensorDatumConcept T >
void Aftr::SensorDatumTraversal<T>::setTimeMultiplier( double multiplier )
{
   this->timeMultiplier = multiplier;
   this->setPlaybackLocationIdx( this->curIdx, std::chrono::utc_clock::now() );
}

template< Aftr::SensorDatumConcept T >
double Aftr::SensorDatumTraversal<T>::getTimeMultiplier()
{
   return this->timeMultiplier;
}

template< Aftr::SensorDatumConcept T >
double Aftr::SensorDatumTraversal<T>::getPlaybackLocation() const
{
   double t = static_cast<double>( this->curIdx ) / static_cast<double>( this->dat.size() );
   return t;
}

template< Aftr::SensorDatumConcept T >
size_t Aftr::SensorDatumTraversal<T>::getPlaybackLocationIdx() const
{
   return this->curIdx;
}

template< Aftr::SensorDatumConcept T >
void Aftr::SensorDatumTraversal<T>::setPlaybackLocation( double parametricProgressThruData, std::chrono::utc_clock::time_point const& timeNow )
{
   if( parametricProgressThruData < 0 || parametricProgressThruData > 1.0 )
   {
      fmt::print( "WARNING: {:s}: cannot set parametric playback location outside domain [0,1]\n", AFTR_FILE_LINE_STR );
      return;
   }

   double dIdx = static_cast<double>( this->dat.size() ) * parametricProgressThruData;
   this->curIdx = static_cast<size_t>( dIdx );
   if( curIdx >= this->dat.size() )
      curIdx = this->dat.size() - 1;

   this->setPlaybackLocationIdx( this->curIdx, timeNow );
}

template< Aftr::SensorDatumConcept T >
void Aftr::SensorDatumTraversal<T>::setPlaybackLocationIdx( size_t datumIndex, std::chrono::utc_clock::time_point const& timeNow )
{
   if( this->dat.size() == 0 )
      return;
   if( datumIndex > this->dat.size()-1 )
   {
      fmt::print( "WARNING: {:s}: cannot set playback location index beyond last datum...\n"
                  "   user tried to set curIdx to {:d}, but max valid value is ",
                  AFTR_FILE_LINE_STR, datumIndex, this->dat.size() - 1 );
      // std::cout << "WARNING: " << AFTR_FILE_LINE_STR << " cannot set playback location index beyond last datum...\n"
      //    << "   user tried to set curIdx to " << datumIndex << ", but max valid value is " 
      //    << this->dat.size()-1 << "... Ignoring this invocation...\n";
      return;
   }
   this->curIdx = datumIndex;

   //calculate virtual clock time of curIdx given timeMultiplier
   ////std::chrono::duration< int64_t, std::micro > vDurCurIdx = std::chrono::duration_cast<std::chrono::microseconds>( ptr( this->dat[this->curIdx] )->getDuration() );
   auto t_curIdx = ptr( this->dat[this->curIdx] )->getTime(); //timestamp of this current datum

   //adjust for constant time offset in log file (opposite of what we do in getNextNearestDatum (subtract & divide inside this method versus multiply then add in other method)
   ////vDurCurIdx -= std::chrono::duration_cast<std::chrono::microseconds>( this->timeAtFirstDatum );
   auto delta_t = t_curIdx - this->timeAtFirstDatum; //duration from beginning of data set to this current datum
   
   //convert vDurCurIdx to a double so it can be divided by timeMultiplier w/o integer truncation
   ////double vDurCurIdxDbl = static_cast<double>( std::chrono::duration_cast<std::chrono::microseconds>( vDurCurIdx ).count() );
   double vDurCurIdxDbl = static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>( delta_t ).count());

   //divide by timeMultiplier
   if( !Aftr::isFloatZero( this->timeMultiplier, 0.000001 ) )
      vDurCurIdxDbl /= this->timeMultiplier;

   //convert back to a chrono::duration from a double
   ////vDurCurIdx = std::chrono::duration< int64_t, std::micro >( static_cast<int64_t>( vDurCurIdxDbl ) );
   delta_t = std::chrono::duration< int64_t, std::micro >( static_cast<int64_t>(vDurCurIdxDbl) ); //time-multiplier adjusted duration



   ////auto timePt = timeNow - vDurCurIdx;
   auto timePt = timeNow - delta_t;
   auto diff = timePt - this->startTime;

   this->startTime += diff;
}


template< Aftr::SensorDatumConcept T >
const T& Aftr::SensorDatumTraversal<T>::getNextDatum() noexcept
{
   size_t DAT_SIZE = dat.size();
   if( DAT_SIZE == 0 )
   {
      fmt::print( "WARNING: {:s}: SensorDatumTraversal has a data set of size 0...\n", AFTR_FILE_LINE_STR );
      // std::cout << "WARNING: " << AFTR_FILE_LINE_STR << " SensorDatumTraversal has a data set of size 0...\n";
      static T dummy;
      return dummy;
   }

   if( this->timeMultiplier >= 0 ) // we are marching forwards in time
      if( curIdx < (DAT_SIZE - 1) )
         ++curIdx;
   else //we are marching backwards in time
      if( curIdx > 0 )
         --this->curIdx;
   return this->dat[this->curIdx];

}

template< Aftr::SensorDatumConcept T >
const T& Aftr::SensorDatumTraversal<T>::getNextNearestDatum( bool interpolate )
{
   size_t DAT_SIZE = dat.size();
   if( DAT_SIZE == 0 )
   {
      fmt::print( "WARNING: {:s}: SensorDatumTraversal has a data set of size 0...\n", AFTR_FILE_LINE_STR );
      //std::cout << "WARNING: " << AFTR_FILE_LINE_STR << " SensorDatumTraversal has a data set of size 0...\n";
      static T dummy;
      return dummy;      
   }

   //duration stores the wall clock time elapsed since the last call to initPlayback.
   //Since we assume we want to traverse the data at wall time (real world time), this is an accurate indicator
   //or wall time.
   auto durationAlongDataSet = std::chrono::utc_clock::now() - this->startTime;   
   int64_t rawDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( durationAlongDataSet ).count();
   //std::cout << rawDurationUSec << "usec has elapsed...\n";
   rawDurationUSec = static_cast<int64_t>( static_cast<double>( rawDurationUSec ) * this->timeMultiplier ); //scale by time multiplier
   //std::cout << rawDurationUSec << "usec has elapsed after multiplier...\n";
   
   //now that we've scaled the rawDurationUSec by timeMultiplier, we have to convert it back to a duration and add in starting offset time and timewarp time
   durationAlongDataSet = std::chrono::duration< int64_t, std::micro >( rawDurationUSec );
   ////durationAlongDataSet += this->timeDurationAtFirstDatum; //account for non-zero start time (that is positive) as well as for a call to setPlaybackLocation
   auto timeAlongDataSet = durationAlongDataSet + this->timeAtFirstDatum;
   ////rawDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( durationAlongDataSet ).count();


   ////auto rawDatumTimeDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx] )->getDuration() ).count();
   auto timeAtThisDatum = ptr( this->dat[this->curIdx] )->getTime();

   //we ought to index into the data such that we find the time closest to "duration" assuming the log file's time begins at zero
   if( this->timeMultiplier >= 0 ) // we are marching forwards in time
   {
      ////while( rawDatumTimeDurationUSec < rawDurationUSec )
      while( timeAtThisDatum < timeAlongDataSet )
      {
         if( curIdx < (DAT_SIZE - 1) ) ++curIdx;
         ////rawDatumTimeDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx] )->getDuration() ).count();
         timeAtThisDatum = ptr( this->dat[this->curIdx] )->getTime();
         if( curIdx == (DAT_SIZE - 1) ) break; //we've reached the last data point, so we return the most recent point we have
      }
      if( interpolate )
      {
         //interpolate the data in between curIdx and curIdx-1, by parametric value 
         //interp( curIdx, curIdx-1, t )
         ////auto upper = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx] )->getDuration() ).count();
         ////auto lower = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx-1] )->getDuration() ).count();
         auto upperTime = ptr( dat[curIdx] )->getTime();
         auto lowerTime = ptr( dat[curIdx - 1] )->getTime();
         ////auto domain = upper - lower;
         auto domainDur = upperTime - lowerTime;
         ////auto deltaT = rawDurationUSec - lower;
         auto deltaT = timeAlongDataSet - lowerTime;
         ////double t = static_cast<double>( deltaT ) / static_cast<double>( domain );
         double t = static_cast<double>( deltaT.count() ) / static_cast<double>( domainDur.count() );
         //interp( lower, upper, t ); //NOT YET TESTED THE INTERPOLATION BLOCK
      }

   }
   else //we are marching backwards in time - THIS HAS NOT YET BEEN TESTED - SLN
   {
      //swap the sign on rawDurationUSec, swap less-than to greater-than, and decrement curIdx
      //std::cout << "BACKWARDS IN TIME...\n";

      ////while( rawDatumTimeDurationUSec > rawDurationUSec )
      while( timeAtThisDatum > timeAlongDataSet )
      {
         if( curIdx > 0 ) --curIdx;
         ////rawDatumTimeDurationUSec = std::chrono::duration_cast<std::chrono::microseconds>( ptr( dat[curIdx] )->getDuration() ).count();
         timeAtThisDatum = ptr( this->dat[this->curIdx] )->getTime();
         if( curIdx == 0 ) break; //we've reached the earliest data point, so we return the earliest point we have
      }
      if( interpolate )
      {
         //interpolate the data in between curIdx and curIdx+1
         //interp( curIdx, curIdx+1, t )
      }
   }
   //std::cout << "idx: " << curIdx << "\n";
   return dat[curIdx];
}

/// aaaaaaaaabbbbbbbbbbbcccccccc
/// ^        ^          ^
/// |        |          Data that is after the elements we don't care about (should be trimmed)
/// |        Data we want to capture (create new Dataset of only the b's) - pred first evaluates to true at this element
/// Data that is before the element we don't want (this should be trimmed)
template< Aftr::SensorDatumConcept T >
Aftr::SensorDatumTraversal<T> Aftr::SensorDatumTraversal<T>::trimDataSetTo( std::function<bool( const T& )> pred, 
                                                      int length, Aftr::SensorDatumTraversal<T>&& dat )
{
   // 
   // aaaaaaaaabbbbbbbbbbbcccccccc
   // ^        ^          ^
   // |        |          Data that is after the elements we don't care about (should be trimmed)
   // |        Data we want to capture (create new Dataset of only the b's) - pred first evaluates to true at this element
   // Data that is before the element we don't want (this should be trimmed)

   //std::function<bool( FltDatPt& )> pred = [] () { return false; }

   auto & d = dat.getData();
   auto startIt = std::find_if( d.begin(), d.end(), pred );

   if( startIt == d.end() )
   {
      fmt::print( "{:s}: Trimmed to size zero...\n", AFTR_FILE_LINE_STR );
      //std::cout << AFTR_FILE_LINE_STR << ": Trimmed to size zero...\n";
      dat.dat.clear();
      dat.curIdx = 0;
      //return std::move(dat);
   }

   auto numSelected = std::distance( startIt, d.end() );
   if( numSelected >= static_cast<decltype(numSelected)>(length ) )
   {
      //we can return startId to startIt + length as a new AftrSensorDataTarversal<T>
      dat.dat = std::vector<T>( startIt, startIt + length );
      return std::move(dat);
   }
   else
   {
      fmt::print( "ERROR: {:s}: max selectable given start pulse is only {:d}"
                  " datums; however, user wanted to trim {:d} datums. Already removed {:d}"
                  " datums from the beginning... Total input size is {:d}\n",
         AFTR_FILE_LINE_STR, numSelected, length, std::distance( d.begin(), startIt ), d.size() );
      std::abort();
   }

   return Aftr::SensorDatumTraversal<T>{};
}