// -*- C++ -*-

#ifndef XM_XMTIME_H_

//
// C++ interface to the X-Midas precision time specification.
//
// X-Midas represents precision time as 2 double precision numbers:
// integral and fractional seconds since the epoch.  Fractional
// seconds are in the range [0.0, 1.0)
//
// Author:  X-Midas 4.11.0 xmtime.h,v 1c6997824133 2011/11/08 14:49:29 bwc
// Version: $Id: xmtime.h,v 1.1 2012/05/23 18:47:08 ntn Exp $
//
// ///////////////////////////////////////////// Include Files

#include <sys/time.h>
#include <assert.h>

#include <string>
#include <utility>
using namespace std::rel_ops;

#include <cdefs.h>
#include <midaslib.h>
#include <fintrinsics.h>
#include <math.h>


// ///// Constants

const int_u4 SecondsPerDay = 24*60*60;
const int_u4 SecondsPerHour = 60*60;
const int_u4 SecondsPerMinute = 60;

const real_8 Seconds_1950to1970 = (20.0 * 365.0 + 5.0) * SecondsPerDay;

// ///////////////////////////////////////////// The XMTime Class

class XMTime {

  public:

    // ///// Type definitions

    enum Format_e {
      STANDARD,  // Example:  1998:12:09::23:55:00.000000000
      TCR,       // Example:  343:23:55:00.000000000
      NORAD,     // Example:  98343.99652777
      VAX,       // Example:  09-DEC-1998:23:55:00
      TOD        // Example:  23:55:00.000000000
    };


    // ///// Methods

    //  Compiler-generated copy-constructor and assignment operator are fine
    //  for this class

    // ///// Constructors.

    // Construct an XMTime from different possible representations of
    // time:  current time, one float, two floats, strings, and
    // epoch times from opened X-Midas BLUE headers.
    //
    // The XMTime value that is constructed will be normalized.
    // That is the integral portion will be >= 1.0 and the fractional
    // portion will be in the range [0.0, 1.0).

    // For the one float and two float constructors, if there is more
    // than 1 years worth of time in the specified integral portion
    // will also be rounded to the nearest microseconds to avoid
    // floating-point noise.

    // Construct a time using the system time, current time zone.

    XMTime ()
    {
      m_now(integral_, fractional_);
      normalize();
    }


    // Construct a time using modified Julian day in seconds

    XMTime (real_8 s)
    {
      if (s > 366*SecondsPerDay) {
        integral_ = s;
        fractional_ = 0.0;
        round_integral_to_usec();
        normalize();
      } else {
        integral_ = floor(s);
        fractional_ = s - integral_;
      }
    }


    // Construct a time from a string

    inline XMTime (const std::string& s, Format_e format = STANDARD);


    // Construct a time from integral and fractional seconds
    //
    // The XMTime value that is constructed will be normalized.
    // That is the integral portion will be >= 1.0 and the fractional
    // portion will in the range [0.0, 1.0).

    // If there is more than 1 years worth of time in the
    // specified integral portion will also be rounded to the
    // nearest microseconds to avoid floating-point noise.

    XMTime (real_8 integral, real_8 fractional):
      integral_(integral),
      fractional_(fractional)
    {
      if (integral_ > 366*SecondsPerDay) {
        round_integral_to_usec();
      }
      normalize();
    }


    // Construct a time from an open header, using m_get_epoch.

    XMTime (HEADER& hcb)
    {
      m_get_epoch(hcb, integral_, fractional_);
      normalize();
    }


    // ///// Methods

    // Puts the XMTime epoch into a file header using m_put_epoch.
    // If the file is open and not a pipe then the header will
    // be updated with m_update_header.  If the file is an open pipe
    // then the hcb.timecode and keywords will be copied to the
    // shared memory pipe header.
    //
    // See m_put_epoch for details.

  void put_epoch (HEADER& hcb, bool_4 toQuads=true, bool_4 update_header=true)
    {
      m_put_epoch(hcb, integral_, fractional_, toQuads);
      // DR 526016 - Make sure the file is open before updating
      if (hcb.open && update_header) {
        if (hcb.pipe) {
          // For a pipe only copy the relevent time fields
          HEADER& pcb = *midas::to_ptr<HEADER*>(hcb.lph);
          pcb.timecode = hcb.timecode;
          // TC_PREC goes into the keywords, so they must be
          // copied as well.
          pcb.keylength = hcb.keylength;
          m_move(&hcb.keywords, &pcb.keywords, L_keywords);
        } else {
          m_update_header(hcb);
        }
      }
    }


    // Return XMTime as a formatted string.

    inline std::string asString (Format_e format = STANDARD) const;


    // Normalize the XMTime instance, meaning that integral_ will
    // always be >= 1.0 and fractional_ will always be in
    // the range [0.0, 1.0).

    void normalize ()
    {
      real_8 fint = integral_ - floor(integral_);
      if (fabs( fint ) > 0.0) {
	fractional_ += fint;
	integral_   -= fint;
      }

      if (fabs(fractional_) >= 1.0) {
	real_8 ifrac = floor(fractional_);
	integral_   += ifrac;
	fractional_ -= ifrac;
      }
      if (fractional_ < 0.0) {
	fractional_ += 1.0;
	integral_ -= 1;
      }

      #ifdef _DEBUG
        assert(!(fabs(integral_ - floor(integral_)) > 0.0));
        assert(fabs(fractional_) < 1.0);
        assert(0.0 <= fractional_);
      #endif
    }

    // Rounds any non-integral portion of the integral_ value to
    // microseconds and adds the extra fractional portion to the
    // existing fractional_.  The resulting integral_ value
    // will be a whole number.

    // If a double is used for a J1950 time the integral portion
    // cannot not have better than microsecond precision.
    // Floating-point noise will result if more precision is attempted.
    // This routine does not limit the precision of the fractional
    // portion.

    void round_integral_to_usec ()
    {
      real_8 dintegral = DNINT(integral_);
      if (dintegral != integral_) {
        real_8 extra = integral_ - dintegral;
        fractional_ += DNINT(extra * 1e6) / 1e6;
        integral_ = dintegral;
      }
    }

    // ///// Inspectors

    real_8 integralSeconds () const    { return integral_; }
    real_8 fractionalSeconds () const  { return fractional_; }
    real_8 seconds () const            { return integral_ + fractional_; }


    // ///// Operators

    // ///// Relational operators

    bool operator< (const XMTime& t) const
    { return (fabs(integral_ - t.integral_) > 0.0) ?
	(integral_ < t.integral_) : (fractional_ < t.fractional_); }


    bool operator== (const XMTime& t) const
    { return  !(fabs(integral_ - t.integral_) > 0.0)  &&  !(fabs(fractional_ - t.fractional_) > 0.0); };


    // (Remainder of relational operators provided by <utility>)


    // Check equality within tolerance.  The difference between the
    // two values must be strictly less than the tolerance indicated.
    // Defaults to a tolerance of 1 nanosecond.

    bool eqt (const XMTime& t, real_8 tolerance = 1e-9) const
    { return fabs(XMTime(*this - t).seconds()) < tolerance; }


    // ///// Cast operators

    // Return XMTime as formatted string.  The format is
    // STANDARD.  Use XMTime::asString() if you want your time
    // displayed in another format.

    operator std::string () const
    { return asString(); }


    // ///// Arithmetic operators

    // XMTime instances can represent durations, so the result
    // of operator- () is another XMTime.

    friend inline XMTime operator- (const XMTime& t1, const XMTime& t2);
    inline XMTime& operator-= (const XMTime& t);


    // XMTimes can also be added together.  When an XMTime is
    // small (24 hours or less from the epoch), it is usually
    // treated by X-Midas as a relative time.  Thus, although
    // adding two absolute times will probably result in overflow,
    // the operation is permitted for those cases when one or
    // both XMTimes are functioning as relative times.

    friend inline XMTime operator+ (const XMTime& t1, const XMTime& t2);
    inline XMTime& operator+= (const XMTime& t);

  private:

    // ///// Data Members

    // The integral and fractional parts of this XMTime.

    real_8	integral_;
    real_8	fractional_;


};                                      // XMTime



// ///////////////////////////////////////////// Inlined XMTime Methods

inline XMTime::XMTime (const std::string& s, XMTime::Format_e format)
{
  std::string std_format;
  switch (format) {
  case STANDARD:
    m_time_format(s, '_', std_format, integral_, fractional_, true);
    break;
  case TCR:
    m_time_format(s, 'T', std_format, integral_, fractional_, true);
    break;
  case NORAD:
    m_time_format(s, 'N', std_format, integral_, fractional_, true);
    break;
  case VAX:
    m_time_format(s, 'V', std_format, integral_, fractional_, true);
    break;
  case TOD:
    m_tod2times(s, integral_, fractional_);
    break;
  default:
    assert(0);
  }
  normalize();
}                                     // XMTime constructor


inline XMTime& XMTime::operator-= (const XMTime& other)
{
  integral_ -= other.integral_;
  fractional_ -= other.fractional_;
  normalize();
  return *this;
}


inline XMTime& XMTime::operator+= (const XMTime& other)
{
  integral_ += other.integral_;
  fractional_ += other.fractional_;
  normalize();
  return *this;
}


inline std::string XMTime::asString (Format_e format) const
{
  std::string s;
  switch (format) {
  case STANDARD:
    m_times2str(integral_, fractional_, '_', s);
    break;
  case TCR:
    m_times2str(integral_, fractional_, 'T', s);
    break;
  case NORAD:
    m_times2str(integral_, fractional_, 'N', s);
    break;
  case VAX:
    m_times2str(integral_, fractional_, 'V', s);
    break;
  case TOD:
    m_sec2tod(fmod(integral_, 86400) + fractional_, s);
    break;
  default:
    assert(0);
  }
  return s;
}


// ///////////////////////////////////////////// Global Inline Methods

inline XMTime operator+ (const XMTime& t1, const XMTime& t2)
{
  XMTime temp(t1);
  temp.integral_   += t2.integral_;
  temp.fractional_ += t2.fractional_;
  temp.normalize();
  return temp;
}


inline XMTime operator- (const XMTime& t1, const XMTime& t2)
{
  XMTime temp(t1);
  temp.integral_   -= t2.integral_;
  temp.fractional_ -= t2.fractional_;
  temp.normalize();
  return temp;
}


inline std::ostream& operator<< (std::ostream& strm, const XMTime& t)
{
  strm << t.asString();
  return strm;
}


inline std::istream& operator>> (std::istream& strm, XMTime& t)
{
  std::string s;
  strm >> s;
  t = XMTime(s);
  return strm;
}


#define XM_XMTIME_H_
#endif // XM_XMTIME_H_

