//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <ldastoolsal_config.h>

#include <math.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>

#include <unistd.h>

#include <cmath>
#include <cstdlib>

#include <fstream>
#include <iomanip>
#include <list>
#include <sstream>

#include "ldastoolsal/ErrorLog.hh"
#include "ldastoolsal/gpstime.hh"
#include "ldastoolsal/ReadWriteLock.hh"

using LDASTools::AL::ErrorLog;
using LDASTools::AL::JournalLog;
using LDASTools::AL::ReadWriteLock;

namespace
{
    static const INT_4U SECONDS_PER_MINUTE = 60;
    static const INT_4U SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
    static const INT_4U SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;

    static const INT_4U UNIXGPS = INT_4U( 315964818 );

    inline bool
    normalize( INT_4U& Seconds, INT_4U& Nanoseconds, const bool Log = true )
    {
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        bool retval = true;
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        if ( ( Log ) && ( Nanoseconds >= NANOSECOND_INT_MULTIPLIER ) )
        {
            std::ostringstream msg;

            msg << "Overflow of nanoseconds: " << Nanoseconds;

            JournalLog(
                ErrorLog::ASSERT_FAILURE, __FILE__, __LINE__, msg.str( ) );
            retval = false;
        }
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        const INT_4U old_seconds = Seconds;
        Seconds += Nanoseconds / NANOSECOND_INT_MULTIPLIER;
        Nanoseconds = Nanoseconds % NANOSECOND_INT_MULTIPLIER;
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        if ( ( Log ) && ( old_seconds > Seconds ) )
        {
            std::ostringstream msg;

            msg << "Overflow of seconds: " << old_seconds;

            JournalLog(
                ErrorLog::ASSERT_FAILURE, __FILE__, __LINE__, msg.str( ) );
        }
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        return retval;
    }

    //-------------------------------------------------------------------
    /// Class to keep track of leap seconds
    //-------------------------------------------------------------------
    class offset_data_type
    {
    public:
        //---------------------------------------------------------------
        /// \brief  Obtain the number of leap seconds
        //---------------------------------------------------------------
        static INT_2U GetLeapSeconds( INT_4U TAI );
        //---------------------------------------------------------------
        /// \brief  Set the filename for the Offset Table
        ///
        /// \param[in] Filename
        ///     The name of the file containing the leap seconds
        ///     data.
        //---------------------------------------------------------------
        static void OffsetTableFilename( const std::string& Filename );

    private:
        //---------------------------------------------------------------
        /// \brief  Julian Date class
        //---------------------------------------------------------------
        struct jd_type
        {
            INT_4U s_day;
            INT_4U s_day_fraction;

            inline jd_type( )
            {
            }

            inline jd_type( INT_4U Day, INT_4U Fraction )
                : s_day( Day ), s_day_fraction( Fraction )
            {
            }

            static inline const jd_type&
            unix_epoc( )
            {
                static const jd_type epoc( 2440587, 5 );
                return epoc;
            }

            inline bool
            operator<( const jd_type& RHS ) const
            {
                if ( s_day != RHS.s_day )
                {
                    return s_day < RHS.s_day;
                }
                return s_day_fraction < RHS.s_day_fraction;
            }

            inline bool
            operator>=( const jd_type& RHS ) const
            {
                return !( *this < RHS );
            }

            inline jd_type
            operator-( const jd_type& RHS ) const
            {
                INT_4U  day_offset = 0;
                jd_type retval;

                if ( s_day_fraction >= RHS.s_day_fraction )
                {
                    retval.s_day_fraction = s_day_fraction - RHS.s_day_fraction;
                }
                else
                {
                    day_offset = 1;
                    retval.s_day_fraction =
                        ( 10 + s_day_fraction ) - RHS.s_day_fraction;
                }
                retval.s_day = s_day - day_offset - RHS.s_day;
                return retval;
            }
        };

        //---------------------------------------------------------------
        /// \brief  Data to keep track of when leap seconds have been added
        //---------------------------------------------------------------
        struct data_type
        {
            jd_type s_jd;
            INT_4U  s_tai;
            REAL_4  s_leapseconds;
            INT_2U  s_unix_leaps;

            inline data_type( )
            {
            }

            inline data_type( jd_type JulianDate, REAL_4 TAI_UTC_Diff )
                : s_jd( JulianDate ), s_tai( 0 ), s_leapseconds( TAI_UTC_Diff ),
                  s_unix_leaps( 0 )
            {
                if ( s_jd >= s_jd.unix_epoc( ) )
                {
                    jd_type unix_days( s_jd - s_jd.unix_epoc( ) );
                    s_tai = unix_days.s_day * SECONDS_PER_DAY;
                    s_unix_leaps = INT_2U( s_leapseconds );
                }
            }
        };

        typedef std::list< data_type > offset_table_type;

        static offset_table_type         m_offset_table;
        static ReadWriteLock::baton_type m_offset_table_lock;
        static ReadWriteLock::baton_type m_offset_table_env_lock;
        static std::string               m_offset_table_filename;
        static time_t                    m_offset_table_time_check;
        static time_t                    m_offset_table_modify_time;
        static bool                      m_offset_table_env_checked;

        static void update_table_filename_from_env( );
        static void update_table( );
        static void update_table( std::istream& Stream );
    };

    //-------------------------------------------------------------------
    /// This table is based on information available at:
    ///   http://maia.usno.navy.mil/ser7/tai-utc.dat
    //-------------------------------------------------------------------
    static const char*
        tai_utc_data( " 1961 JAN  1 =JD 2437300.5  TAI-UTC=   1.4228180 S + "
                      "(MJD - 37300.) X 0.001296 S\n"
                      " 1961 AUG  1 =JD 2437512.5  TAI-UTC=   1.3728180 S + "
                      "(MJD - 37300.) X 0.001296 S\n"
                      " 1962 JAN  1 =JD 2437665.5  TAI-UTC=   1.8458580 S + "
                      "(MJD - 37665.) X 0.0011232S\n"
                      " 1963 NOV  1 =JD 2438334.5  TAI-UTC=   1.9458580 S + "
                      "(MJD - 37665.) X 0.0011232S\n"
                      " 1964 JAN  1 =JD 2438395.5  TAI-UTC=   3.2401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1964 APR  1 =JD 2438486.5  TAI-UTC=   3.3401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1964 SEP  1 =JD 2438639.5  TAI-UTC=   3.4401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1965 JAN  1 =JD 2438761.5  TAI-UTC=   3.5401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1965 MAR  1 =JD 2438820.5  TAI-UTC=   3.6401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1965 JUL  1 =JD 2438942.5  TAI-UTC=   3.7401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1965 SEP  1 =JD 2439004.5  TAI-UTC=   3.8401300 S + "
                      "(MJD - 38761.) X 0.001296 S\n"
                      " 1966 JAN  1 =JD 2439126.5  TAI-UTC=   4.3131700 S + "
                      "(MJD - 39126.) X 0.002592 S\n"
                      " 1968 FEB  1 =JD 2439887.5  TAI-UTC=   4.2131700 S + "
                      "(MJD - 39126.) X 0.002592 S\n"
                      " 1972 JAN  1 =JD 2441317.5  TAI-UTC=  10.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1972 JUL  1 =JD 2441499.5  TAI-UTC=  11.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1973 JAN  1 =JD 2441683.5  TAI-UTC=  12.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1974 JAN  1 =JD 2442048.5  TAI-UTC=  13.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1975 JAN  1 =JD 2442413.5  TAI-UTC=  14.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1976 JAN  1 =JD 2442778.5  TAI-UTC=  15.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1977 JAN  1 =JD 2443144.5  TAI-UTC=  16.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1978 JAN  1 =JD 2443509.5  TAI-UTC=  17.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1979 JAN  1 =JD 2443874.5  TAI-UTC=  18.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1980 JAN  1 =JD 2444239.5  TAI-UTC=  19.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1981 JUL  1 =JD 2444786.5  TAI-UTC=  20.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1982 JUL  1 =JD 2445151.5  TAI-UTC=  21.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1983 JUL  1 =JD 2445516.5  TAI-UTC=  22.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1985 JUL  1 =JD 2446247.5  TAI-UTC=  23.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1988 JAN  1 =JD 2447161.5  TAI-UTC=  24.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1990 JAN  1 =JD 2447892.5  TAI-UTC=  25.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1991 JAN  1 =JD 2448257.5  TAI-UTC=  26.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1992 JUL  1 =JD 2448804.5  TAI-UTC=  27.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1993 JUL  1 =JD 2449169.5  TAI-UTC=  28.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1994 JUL  1 =JD 2449534.5  TAI-UTC=  29.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1996 JAN  1 =JD 2450083.5  TAI-UTC=  30.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1997 JUL  1 =JD 2450630.5  TAI-UTC=  31.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 1999 JAN  1 =JD 2451179.5  TAI-UTC=  32.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 2006 JAN  1 =JD 2453736.5  TAI-UTC=  33.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 2009 JAN  1 =JD 2454832.5  TAI-UTC=  34.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 2012 JUL  1 =JD 2456109.5  TAI-UTC=  35.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 2015 JUL  1 =JD 2457204.5  TAI-UTC=  36.0       S + "
                      "(MJD - 41317.) X 0.0      S\n"
                      " 2017 JAN  1 =JD 2457754.5  TAI-UTC=  37.0       S + "
                      "(MJD - 41317.) X 0.0      S\n" );

    INT_4U
    utc_adjust_seconds( INT_4U Seconds )
    {
        return ( Seconds + offset_data_type::GetLeapSeconds( Seconds ) -
                 UNIXGPS );
    }

} // namespace

namespace LDASTools
{
    namespace AL
    {
        //---------------------------------------------------------------
        //---------------------------------------------------------------
        const GPSTime::unit_type GPSTime::DEFAULT_TIME_UNIT_TYPE = GPSTime::GPS;

        GPSTime
        GPSTime::NowGPSTime( )
        {
            struct timeval now;

            gettimeofday( &now, 0 );

            const INT_4U seconds = utc_adjust_seconds( now.tv_sec );
            const INT_4U nanoseconds = now.tv_usec * 1000UL;

            return GPSTime( seconds, nanoseconds, DEFAULT_TIME_UNIT_TYPE );
        }

        GPSTime::GPSTime( ) : m_seconds( 0 ), m_nanoseconds( 0 )
        {
        }

        GPSTime::GPSTime( const INT_4U seconds,
                          const INT_4U nanoseconds,
                          unit_type    Units )
            : m_seconds( seconds ), m_nanoseconds( nanoseconds )
        {
            normalize( m_seconds, m_nanoseconds );
            if ( m_nanoseconds >= 1000000000 )
            {
                throw std::out_of_range(
                    "GPSTime::GPSTime(seconds, nanoseconds): "
                    "nanoseconds >= 1000000000" );
            }

            if ( seconds >= 4294967296.0 )
            {
                throw std::out_of_range(
                    "GPSTime::GPSTime(seconds, nanoseconds): "
                    "seconds later than GPS range" );
            }

            switch ( Units )
            {
            case UTC:
                m_seconds = utc_adjust_seconds( m_seconds );
                break;
            case GPS:
                /// Do nothing for the time in in the proper order.
                break;
            }
        }

        GPSTime::GPSTime( const GPSTime& argument )
            : m_seconds( argument.m_seconds ),
              m_nanoseconds( argument.m_nanoseconds )
        {
        }

        GPSTime::~GPSTime( )
        {
        }

        INT_2U
        GPSTime::GetLeapSeconds( ) const
        {
            INT_4U seconds = ( UNIXGPS + m_seconds );

            return offset_data_type::GetLeapSeconds( seconds );
        }

        void
        GPSTime::Now( )
        {
            *this = GPSTime::NowGPSTime( );
        }

        GPSTime&
        GPSTime::operator=( const GPSTime& rhs )
        {
            if ( &rhs != this )
            {
                m_seconds = rhs.m_seconds;
                m_nanoseconds = rhs.m_nanoseconds;
            }

            return *this;
        }

        INT_4U
        GPSTime::GetSeconds( const unit_type Units ) const
        {
            INT_4U retval = m_seconds;

            switch ( Units )
            {
            case UTC:
                retval += UNIXGPS - GetLeapSeconds( ) + 1;
                break;
            case GPS:
                // Do nothing as the retval is already in GPS units
                break;
            }

            return retval;
        }

        GPSTime&
        GPSTime::operator+=( const double& offset )
        {
            const double offset_secs = std::floor( offset );
            const double offset_nsecs =
                rint( ( offset - offset_secs ) * 1.0e+09 );
            double seconds = m_seconds + offset_secs;
            double nanoseconds = m_nanoseconds + offset_nsecs;

            if ( nanoseconds >= 1.0e+09 )
            {
                seconds += 1.0;
                nanoseconds -= 1.0e+09;
            }

            if ( seconds < 0 )
            {
                throw std::out_of_range( "GPSTime arithmetic: underflow "
                                         "(resultant earlier than GPS range)" );
            }

            if ( seconds >= 4294967296.0 )
            {
                throw std::out_of_range( "GPSTime arithmetic: overflow "
                                         "(resultant later than GPS range)" );
            }

            m_seconds = (INT_4U)rint( seconds );
            m_nanoseconds = (INT_4U)rint( nanoseconds );

            return *this;
        }

        GPSTime&
        GPSTime::operator-=( const double& rhs )
        {
            return operator+=( -rhs );
        }

        GPSTime
        operator+( const GPSTime& left, const double& right )
        {
            return GPSTime( left ).operator+=( right );
        }

        GPSTime
        operator+( const double& left, const GPSTime& right )
        {
            return GPSTime( right ).operator+=( left );
        }

        GPSTime
        operator-( const GPSTime& left, const double& right )
        {
            return GPSTime( left ).operator-=( right );
        }

        double
        operator-( const GPSTime& left, const GPSTime& right )
        {
            return ( double( left.GetSeconds( ) ) -
                     double( right.GetSeconds( ) ) ) +
                ( ( double( left.GetNanoseconds( ) ) -
                    double( right.GetNanoseconds( ) ) ) /
                  1000000000.0 );
        }

        bool
        operator==( const GPSTime& left, const GPSTime& right )
        {
            return ( left.GetSeconds( ) == right.GetSeconds( ) ) &&
                ( left.GetNanoseconds( ) == right.GetNanoseconds( ) );
        }

        bool
        operator!=( const GPSTime& left, const GPSTime& right )
        {
            return ( left.GetSeconds( ) != right.GetSeconds( ) ) ||
                ( left.GetNanoseconds( ) != right.GetNanoseconds( ) );
        }

        bool
        operator<( const GPSTime& left, const GPSTime& right )
        {
            return ( ( left.GetSeconds( ) < right.GetSeconds( ) ) ||
                     ( ( left.GetSeconds( ) == right.GetSeconds( ) ) &&
                       ( left.GetNanoseconds( ) < right.GetNanoseconds( ) ) ) );
        }

        bool
        operator>( const GPSTime& left, const GPSTime& right )
        {
            return ( ( left.GetSeconds( ) > right.GetSeconds( ) ) ||
                     ( ( left.GetSeconds( ) == right.GetSeconds( ) ) &&
                       ( left.GetNanoseconds( ) > right.GetNanoseconds( ) ) ) );
        }

        bool
        operator<=( const GPSTime& left, const GPSTime& right )
        {
            return (
                ( left.GetSeconds( ) < right.GetSeconds( ) ) ||
                ( ( left.GetSeconds( ) == right.GetSeconds( ) ) &&
                  ( left.GetNanoseconds( ) <= right.GetNanoseconds( ) ) ) );
        }

        bool
        operator>=( const GPSTime& left, const GPSTime& right )
        {
            return (
                ( left.GetSeconds( ) > right.GetSeconds( ) ) ||
                ( ( left.GetSeconds( ) == right.GetSeconds( ) ) &&
                  ( left.GetNanoseconds( ) >= right.GetNanoseconds( ) ) ) );
        }

        std::ostream&
        operator<<( std::ostream& Stream, const GPSTime& Time )
        {
            std::ostringstream msg;

            msg << Time.GetSeconds( ) << "." << std::setfill( '0' )
                << std::setw( 9 ) << Time.GetNanoseconds( );
            Stream << msg.str( );

            return Stream;
        }

        void
        SetOffsetTableFilename( const std::string& Filename )
        {
            offset_data_type::OffsetTableFilename( Filename );
        }

        GPSTime
        GPSTime::now( )
        {
            struct timeval current;

            gettimeofday( &current, 0 );

            INT_4U seconds = utc_adjust_seconds( current.tv_sec );
            INT_4U nanoseconds = current.tv_usec * 1000UL;

            /// Normalize here to ensure that there will be no exceptions
            normalize( seconds, nanoseconds, false );

            /// Create the structure.
            return GPSTime( seconds, nanoseconds );
        }

    } // namespace AL
} // namespace LDASTools

//-----------------------------------------------------------------------
/// Local functions and classes
//-----------------------------------------------------------------------
namespace
{
    //-------------------------------------------------------------------
    /// offset_data_type
    //-------------------------------------------------------------------
    offset_data_type::offset_table_type offset_data_type::m_offset_table;
    ReadWriteLock::baton_type           offset_data_type::m_offset_table_lock =
        ReadWriteLock::Baton( );
    ReadWriteLock::baton_type offset_data_type::m_offset_table_env_lock =
        ReadWriteLock::Baton( );

    std::string offset_data_type::m_offset_table_filename;
    time_t      offset_data_type::m_offset_table_time_check = 0;
    time_t      offset_data_type::m_offset_table_modify_time = 0;
    bool        offset_data_type::m_offset_table_env_checked = false;

    INT_2U
    offset_data_type::GetLeapSeconds( const INT_4U TAI )
    {
        //---------------------------------------------------------------
        /// Ensure the table is up to date
        //---------------------------------------------------------------
        update_table( );

        //---------------------------------------------------------------
        /// Calculate the leap seconds
        //---------------------------------------------------------------
        ReadWriteLock lock(
            m_offset_table_lock, ReadWriteLock::READ, __FILE__, __LINE__ );

        offset_table_type::reverse_iterator pos = m_offset_table.rbegin( );
        offset_table_type::reverse_iterator last = m_offset_table.rend( );
        INT_2U                              leaps = 0;

        while ( ( pos != last ) && ( ( leaps = pos->s_unix_leaps - 1 ) ) &&
                ( ( pos->s_tai + leaps ) > TAI ) )
        {
            ++pos;
        }
        if ( pos == last )
        {
            return INT_2U( 0 );
        }
        return pos->s_unix_leaps;
    }

    void
    offset_data_type::OffsetTableFilename( const std::string& Filename )
    {
        ReadWriteLock lock(
            m_offset_table_lock, ReadWriteLock::WRITE, __FILE__, __LINE__ );

        if ( Filename.compare( m_offset_table_filename ) != 0 )
        {
            m_offset_table_filename = Filename;
            m_offset_table_time_check = 0;
            m_offset_table_modify_time = 0;
        }
    }

    inline void
    offset_data_type::update_table_filename_from_env( )
    {
        static const char* LEAPSECOND_ENV_KEY = "GPS_LEAPSECOND_FILE";

        ReadWriteLock lock(
            m_offset_table_env_lock, ReadWriteLock::WRITE, __FILE__, __LINE__ );

        if ( ( m_offset_table_env_checked == false ) &&
             ( m_offset_table_filename.length( ) == 0 ) &&
             ( std::getenv( LEAPSECOND_ENV_KEY ) != 0 ) )
        {
            OffsetTableFilename(
                std::string( std::getenv( LEAPSECOND_ENV_KEY ) ) );
            m_offset_table_env_checked = true;
        }
    }

    inline void
    offset_data_type::update_table( )
    {
        time_t now;

        struct timeval now_buffer;

        //---------------------------------------------------------------
        // Find out what time it currently is
        //---------------------------------------------------------------
        gettimeofday( &now_buffer, 0 );

        now = now_buffer.tv_sec;

        update_table_filename_from_env( ); // Find out if env set

        if ( ( m_offset_table_filename.length( ) > 0 ) &&
             ( now > m_offset_table_time_check ) )
        {
            //-----------------------------------------------------------
            /// Block others from any modifications
            //-----------------------------------------------------------
            ReadWriteLock lock(
                m_offset_table_lock, ReadWriteLock::WRITE, __FILE__, __LINE__ );

            struct stat stat_buffer;
            int         stat_status = -1;

            //-----------------------------------------------------------
            /// Secondary check to ensure that the update really needs to
            ///   happen
            //-----------------------------------------------------------
            if ( ( m_offset_table_filename.length( ) > 0 ) &&
                 ( now > m_offset_table_time_check ) &&
                 ( ( stat_status = stat( m_offset_table_filename.c_str( ),
                                         &stat_buffer ) ) == 0 ) &&
                 ( stat_buffer.st_mtime > m_offset_table_modify_time ) )
            {
                std::ifstream f( m_offset_table_filename.c_str( ) );

                update_table( f );
                f.close( );
            }
            //-----------------------------------------------------------
            /// Establish next time to check for changes.
            //-----------------------------------------------------------
            if ( stat_status == 0 )
            {
                m_offset_table_modify_time = stat_buffer.st_mtime;
            }
            //-----------------------------------------------------------
            // Check again in 7 days
            //-----------------------------------------------------------
            m_offset_table_time_check = now + ( SECONDS_PER_DAY * 7 );
        }
        else if ( m_offset_table.size( ) == 0 )
        {
            //-----------------------------------------------------------
            /// Block others from any modifications
            //-----------------------------------------------------------
            ReadWriteLock lock(
                m_offset_table_lock, ReadWriteLock::WRITE, __FILE__, __LINE__ );

            //-----------------------------------------------------------
            /// Secondary check to ensure that the update really needs to
            ///   happen
            //-----------------------------------------------------------
            if ( m_offset_table.size( ) == 0 )
            {
                std::istringstream s( tai_utc_data );
                update_table( s );
            }
        }
    }

    inline void
    offset_data_type::update_table( std::istream& Stream )
    {
        offset_table_type tmp_table;

        INT_2U      year;
        std::string month;
        INT_2U      day;
        std::string ignore;
        jd_type     julian_date;
        REAL_4      tai_utc_sec;
        char        buffer[ 256 ];
        char        decimal;

        while ( Stream.good( ) )
        {
            //-----------------------------------------------------------
            /// Read the fields of interest
            //-----------------------------------------------------------
            Stream >> year >> month >> day >> ignore >> julian_date.s_day >>
                decimal >> julian_date.s_day_fraction >> ignore >> tai_utc_sec;
            if ( Stream.good( ) )
            {
                //-------------------------------------------------------
                /// Read to end of line
                //-------------------------------------------------------
                Stream.getline( buffer, sizeof( buffer ) - 1, '\n' );
                //-------------------------------------------------------
                /// Add entry to list
                //-------------------------------------------------------
                if ( julian_date >= julian_date.unix_epoc( ) )
                {
                    tmp_table.push_back(
                        data_type( julian_date, tai_utc_sec ) );
                }
            }
        }
        m_offset_table = tmp_table;
    }
} // namespace
