//
// 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 <sys/time.h>

#include <errno.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <cassert>

#include <string>
#include <iostream>
#include <sstream>

#include "ldastoolsal/DeadLockDetector.hh"
#include "ldastoolsal/IOLock.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/ReadWriteLock.hh"
#include "ldastoolsal/Thread.hh"

#include "Thread.icc"
#include "ReadWriteLockBaton.cc"
#include "ReadWriteLockImpl.cc"
#include "MutexLockBaton.cc"

using LDASTools::AL::IOLock;

#define VERBOSE_RETRY_LOGGING 1

#if DEAD_LOCK_DETECTOR_ENABLED
using LDASTools::AL::DeadLockDetector;
#endif /* DEAD_LOCK_DETECTOR_ENABLED */

namespace
{
    using LDASTools::AL::ReadWriteLock;

#if 0
  struct timespec LockAcquisitionInterval = {
    0,
    250000000,
  };
#endif /* 0 */

#if 0
  //---------------------------------------------------------------------
  //---------------------------------------------------------------------
  std::string
  log ( const int Error,
	const std::string& Message,
	const std::string& Level = "ERROR" )
  {
    std::ostringstream	msg;

    msg << Level << ": ReadWriteLock: " << Message
	<< " Error Code: " << Error
	<< " system error: " << strerror( Error );

    IOLock::baton_type	io_baton( IOLock::GetKey( std::cerr ) );

    {
      LDASTools::AL::MutexLock	l( io_baton, __FILE__, __LINE__ );

      std::cerr << msg.str( ) << std::endl;
    }
    return msg.str( );
  }
#endif /* 0 */

#if 0
  //---------------------------------------------------------------------
  //---------------------------------------------------------------------
  inline int
  get_lock( ReadWriteLock::baton_type::impl::handle_type* Lock,
	    ReadWriteLock::baton_type::impl::lock_mode Mode,
	    bool TryLock )
  {
    typedef ReadWriteLock::baton_type::impl::lock_mode lock_mode;
    int retval = 0;

    switch( Mode )
    {
    case lock_mode::NONE:
      /// \todo
      ///    Should never be trying to set the lock to NONE;
      ///    should throw an exception.
      break;
    case lock_mode::READ:
      retval = ( TryLock )
	? pthread_rwlock_tryrdlock( Lock )
	: pthread_rwlock_rdlock( Lock );
	break;
    case lock_mode::WRITE:
      retval = ( TryLock )
	? pthread_rwlock_trywrlock( Lock )
	: pthread_rwlock_wrlock( Lock );
	break;
    }
    return retval;
  }

  //---------------------------------------------------------------------
  //---------------------------------------------------------------------
  inline int
  get_lock( ReadWriteLock::baton_type::impl::handle_type* Lock,
	    ReadWriteLock::baton_type::impl::lock_mode Mode,
	    const struct timespec& RelTimeout,
	    const struct timespec& AbsStop )
  {
    typedef ReadWriteLock::baton_type::impl::lock_mode lock_mode;

    int retval = 0;
#if defined( _POSIX_TIMEOUTS ) && ( _POSIX_TIMEOUTS - 200112L ) >= 0L
    /* POSIX Timeouts are supported - option group [TMO] */
#if defined( _POSIX_THREADS ) && ( _POSIX_THREADS - 200112L ) >= 0L
    /* POSIX threads are supported - option group [THR] */

    struct timespec	timeout;

#if HAVE_CLOCK_GETTIME
    clock_gettime( CLOCK_REALTIME, &timeout );
#else /* HAVE_CLOCK-GETTIME */
    {
      struct timeval tv;

      gettimeofday( &tv, NULL );

      timeout.tv_sec = tv.tv_sec;
      timeout.tv_nsec = tv.tv_usec * 1000;
    }
#endif /* HAVE_CLOCK_GETTIME */

    if ( AbsStop < timeout )
    {
      throw std::runtime_error( "TIMEDOUT" );
    }
    timeout.tv_sec += RelTimeout.tv_sec;
    timeout.tv_nsec += RelTimeout.tv_nsec;
    normalize( timeout );

    switch( Mode )
    {
    case lock_mode::NONE:
      break;
    case lock_mode::READ:
      retval = pthread_rwlock_timedrdlock( Lock, &timeout );
      break;
    case lock_mode::WRITE:
      retval = pthread_rwlock_timedwrlock( Lock, &timeout );
      break;
    }
#else
    switch( Mode )
    {
    case lock_mode::NONE:
      break;
    case lock_mode::READ:
      retval = pthread_rwlock_rdlock( Lock );
      break;
    case lock_mode::WRITE:
      retval = pthread_rwlock_wrlock( Lock );
      break;
    }
#endif
#endif
    return retval;
  }
#endif /* 0 */

#if DEAD_LOCK_DETECTOR_ENABLED
    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    // If a pending deadlock is detected, then an exception will be thrown.
    inline void
    log_lock( DeadLockDetector::state_info_type& Info )
    {
        DeadLockDetector::SetState( Info );
    }

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    inline void
    log_lock_extended( DeadLockDetector::state_info_type& Info,
                       DeadLockDetector::state_type       State,
                       int                                Error )
    {
        Info.s_state = State;
        Info.s_error = Error;

        DeadLockDetector::SetState( Info );
    }

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    inline void
    log_lock_rwlock( pthread_rwlock_t*            Lock,
                     ReadWriteLock::mode_type     Mode,
                     DeadLockDetector::state_type State,
                     int                          Error,
                     const char*                  Filename,
                     const size_t                 Line )
    {
        DeadLockDetector::state_info_type info(
            Lock,
            ( Mode == ReadWriteLock::READ ) ? DeadLockDetector::RW_READ
                                            : DeadLockDetector::RW_WRITE,
            DeadLockDetector::RELEASED,
            Filename,
            Line );
        log_lock_extended( info, State, Error );
    }
#else /* DEAD_LOCK_DETECTOR_ENABLED */
#define log_lock( Info )
#define log_lock_extended( Info, State, Error )
#define log_lock_rwlock( Lock, Mode, State, Error, Filename, Line )
#endif /* DEAD_LOCK_DETECTOR_ENABLED */

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    const char*
    syserror( int Code )
    {
        static char buffer[ 1024 ];

        //-------------------------------------------------------------------
        // NOTE: The conditional is in place to prevent the generation of
        //   a warning message about the return value never being used.
        //-------------------------------------------------------------------
        if ( strerror_r( Code, buffer, sizeof( buffer ) ) )
        {
            return buffer;
        }
        return buffer;
    }

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    inline void
    suspend( size_t Seconds, size_t NanoSeconds )
    {
        struct timespec s;

        s.tv_sec = (time_t)Seconds;
        s.tv_nsec = NanoSeconds;

        nanosleep( &s, &s );
    }

} // namespace

namespace LDASTools
{
    namespace AL
    {
        //===================================================================
        // ReadWriteLock::baton_type::impl
        //===================================================================

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        struct timespec
            ReadWriteLock::baton_type::impl::LockAcquisitionInterval = {
                0,
                250000000,
            };

        //===================================================================
        // ReadWriteLock::baton_type
        //===================================================================
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        int ReadWriteLock::baton_type::retry_count_max = 10;

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::baton_type::baton_type( bool Logging )
            : p( new impl( Logging ) )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void*
        ReadWriteLock::baton_type::Handle( )
        {
            return *p;
        }

        //-------------------------------------------------------------------
        /// \code{.cpp}
        /// try {
        ///   l.Lock( ReadWriteLock::READ, true, __FILE__, __LINE__ );
        /// }
        /// catch( const ReadWrieLock::BusyError& Exception )
        /// {
        ///   // Do something since the lock could not be acquired
        ///   ...
        /// }
        /// catch( const ReadWrieLock::DeadLockError& Exception )
        /// {
        ///   // Do something since the lock could not be acquired
        ///   ...
        /// }
        /// \endcode
        //-------------------------------------------------------------------
        void
        ReadWriteLock::baton_type::Lock( mode_type         Mode,
                                         bool              TryLock,
                                         const char* const File,
                                         const size_t      Line )
        {
            int retry_count = 0;

        retry_lock:
            try
            {
                p->Lock( Mode, TryLock );
                log_lock_rwlock(
                    *p, Mode, DeadLockDetector::ACQUIRED, 0, File, Line );
            }
            catch ( const ReadWriteLock::BusyError& Error )
            {
                log_lock_rwlock( *p,
                                 Mode,
                                 DeadLockDetector::ACQUISITION_ERROR,
                                 EBUSY,
                                 File,
                                 Line );
                throw;
            }
            catch ( const ReadWriteLock::DeadLockError& Error )
            {
                if ( retry_count < retry_count_max )
                {
                    ++retry_count;
                    suspend( 0, 50000 );
                    goto retry_lock;
                }
            }
            catch ( const std::exception& Error )
            {
                log_lock_rwlock( *p,
                                 Mode,
                                 DeadLockDetector::ACQUISITION_ERROR,
                                 EBUSY,
                                 File,
                                 Line );
                throw;
            }
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        ReadWriteLock::baton_type::Lock( mode_type         Mode,
                                         size_t            Timeout,
                                         const char* const File,
                                         const size_t      Line )
        {
            p->Lock( Mode, Timeout );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        ReadWriteLock::baton_type::Unlock( const char* const File,
                                           const size_t      Line )
        {
            p->Unlock( );
        }

        //===================================================================
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::BusyError::BusyError( ) : std::runtime_error( msg( ) )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        std::string
        ReadWriteLock::BusyError::msg( )
        {
            static const std::string m( syserror( EBUSY ) );

            return m;
        }

        //===================================================================

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::DeadLockError::DeadLockError( )
            : std::runtime_error( msg( ) )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        std::string
        ReadWriteLock::DeadLockError::msg( )
        {
            static const std::string m( syserror( EDEADLK ) );

            return m;
        }

        //===================================================================

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::TimedoutError::TimedoutError( )
            : std::runtime_error( msg( ) )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        std::string
        ReadWriteLock::TimedoutError::msg( )
        {
            static const std::string m( syserror( ETIMEDOUT ) );

            return m;
        }

        //===================================================================

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::ReadWriteLock( baton_type  Lock,
                                      mode_type   Mode,
                                      const char* Filename,
                                      int         Linenum )
            : sync_baton( Lock )
        {
            sync_baton.Lock( Mode, Filename, Linenum );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::ReadWriteLock( baton_type  Lock,
                                      mode_type   Mode,
                                      bool        TryLock,
                                      const char* Filename,
                                      int         Linenum )
            : sync_baton( Lock )
        {
            sync_baton.Lock( Mode, TryLock, Filename, Linenum );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::ReadWriteLock( baton_type  Lock,
                                      mode_type   Mode,
                                      int         TimeoutMS,
                                      const char* Filename,
                                      int         Linenum )
            : sync_baton( Lock )
        {
            sync_baton.Lock( Mode, ( size_t )( TimeoutMS ), Filename, Linenum );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::~ReadWriteLock( )
        {
            Release( __FILE__, __LINE__ );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ReadWriteLock::baton_type
        ReadWriteLock::Baton( )
        {
            baton_type retval;

            return retval;
        }

        int
        ReadWriteLock::Interval( int Value )
        {
            if ( Value >= 0 )
            {
                ReadWriteLock::baton_type::impl::LockAcquisitionInterval
                    .tv_sec = Value / 1000;
                ReadWriteLock::baton_type::impl::LockAcquisitionInterval
                    .tv_nsec = ( Value % 1000 ) * 1000000;
            }
            return ReadWriteLock::baton_type::impl::LockAcquisitionInterval
                       .tv_sec *
                1000 +
                ReadWriteLock::baton_type::impl::LockAcquisitionInterval
                    .tv_nsec /
                1000000;
        }
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        ReadWriteLock::Release( const char* Filename, int Linenum )
        {
            sync_baton.Unlock( Filename, Linenum );
#if 0
      sync_baton.reset( );
#endif /* 0 */
        }

        static int rw_timeout = 0;

        int
        ReadWriteLock::Timeout( int Value )
        {
            if ( Value >= 0 )
            {
                rw_timeout = Value;
            }
            return rw_timeout;
        }

    } // namespace AL
} // namespace LDASTools
