//
// 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 <pthread.h>

#include <sstream>

#include "ldastoolsal/DeadLockDetector.hh"
#include "ldastoolsal/mutexlock.hh"

#include "MutexLockBaton.cc"

#if DEAD_LOCK_DETECTOR_ENABLED
using LDASTools::AL::DeadLockDetector;
using LDASTools::AL::MutexLock;

namespace
{

    /// \brief  Log the thread state
    inline void
    log_lock( DeadLockDetector::state_type State,
              MutexLock::baton_type        Lock,
              const char*                  File,
              unsigned int                 Line )
    {
        if ( Lock.pimpl_->Logging( ) )
        {
            DeadLockDetector::SetState(
                DeadLockDetector::MUTEX, Lock, State, File, Line );
        }
    }
} // namespace

#else /* DEAD_LOCK_DETECTOR_ENABLED */
#define log_lock( State, Lock, File, Line )
#endif /* DEAD_LOCK_DETECTOR_ENABLED */

namespace LDASTools
{
    namespace AL
    {
        //===============================================================
        //===============================================================

        MutexLock::BusyError::BusyError( ) : std::runtime_error( msg( ) )
        {
        }

        std::string
        MutexLock::BusyError::msg( )
        {
            static std::string m( strerror( EBUSY ) );

            return m;
        }

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

        MutexLock::baton_type::baton_type( const char*  File,
                                           unsigned int Line,
                                           bool         Logging )
            : pimpl_( new impl( Logging ) )
        {
            log_lock( DeadLockDetector::INITIALIZED, *this, File, Line );
        }

        void*
        MutexLock::baton_type::Handle( )
        {
            return pimpl_->ref( );
        }

        void
        MutexLock::baton_type::Lock( const char* File, const size_t Line )
        {
            log_lock( DeadLockDetector::PENDING, *this, File, Line );
            try
            {
                pimpl_->lock( );
                log_lock( DeadLockDetector::ACQUIRED, *this, File, Line );
            }
            catch ( const std::exception& Exception )
            {
                log_lock(
                    DeadLockDetector::ACQUISITION_ERROR, *this, File, Line );
                throw;
            }
        }

        void
        MutexLock::baton_type::TryLock( const char* File, const size_t Line )
        {
            pimpl_->trylock( );
        }

        void
        MutexLock::baton_type::Unlock( const char* File, const size_t Line )
        {
            try
            {
                pimpl_->unlock( );
                log_lock( DeadLockDetector::RELEASED, *this, File, Line );
            }
            catch ( const std::exception& Exception )
            {
                throw;
            }
        }

        ///----------------------------------------------------------------------
        ///
        /// This contructor creates an object to ensure the releasing
        /// of the exclusive lock once the object goes out of scope.
        /// The lock held by the object can be released early by calling
        /// the Release method.
        ///
        /// \see Release
        ///----------------------------------------------------------------------
        MutexLock::MutexLock( baton_type         Baton,
                              const char*        File,
                              const unsigned int Line )
            : baton( Baton )
        {
            baton.Lock( File, Line );
        }

        MutexLock::~MutexLock( )
        {
            if ( baton )
            {
                try
                {
                    baton.TryLock( __FILE__, __LINE__ );
                }
                catch ( ... )
                {
                }
            }
            Release( __FILE__, __LINE__ );
        }

        MutexLock::baton_type
        MutexLock::Baton( )
        {
            return baton_type( );
        }

        void
        MutexLock::Release( const char* File, const unsigned int Line )
        {
            if ( baton )
            {
                baton.Unlock( File, Line );
            }
        }

        //-----------------------------------------------------------------------
        /// This routine is used when a thread is canceled and the lock needs to
        /// be release.
        /// It is currently only written to support pthread_cancel_push().
        //-----------------------------------------------------------------------
        void
        MutexLock::ThreadCancel( void*              VLock,
                                 const char*        File,
                                 const unsigned int Line )
        {
            if ( VLock )
            {
                MutexLock* l( reinterpret_cast< MutexLock* >( VLock ) );

                l->Release( File, Line );
            }
        }
    } // namespace AL
} // namespace LDASTools
