//
// 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 <time.h>
#ifndef CLOCK_REALTIME
#include <sys/time.h>
#endif

#include <iostream>
#include <stdexcept>

#include "ldastoolsal/ConditionalVariable.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/System.hh"

#include "MutexLockBaton.cc"

using LDASTools::System::ErrnoMessage;

class cv_baton_type : public LDASTools::AL::MutexLock::baton_type
{
public:
    inline cv_baton_type( const char* File, int LineNumber )
        : baton_type( File, LineNumber )
    {
    }

    using baton_type::pimpl_;
};

namespace LDASTools
{
    namespace AL
    {
        class ConditionalVariable::impl
        {
        public:
            typedef pthread_cond_t cond_type;

            class timeout_error : public std::runtime_error
            {
            public:
                timeout_error( ) : runtime_error( init( ).c_str( ) )
                {
                }

            private:
                static std::string
                init( )
                {
                    std::ostringstream msg;

                    msg << "timeout error: " << ErrnoMessage( );
                    return msg.str( );
                }
            };

            inline impl( ) : baton( __FILE__, __LINE__ )
            {
                pthread_cond_init( &var, NULL );
            }

            inline ~impl( )
            {
                pthread_cond_destroy( &var );
            }

            inline void
            Broadcast( )
            {
                pthread_cond_broadcast( &var );
            }

            inline void
            Signal( )
            {
                pthread_cond_signal( &var );
            }

            inline void
            TimedWait( INT_4U Seconds )
            {
                struct timespec ts;

#if defined( CLOCK_REALTIME )
                clock_gettime( CLOCK_REALTIME, &ts );
#else
                timeval tv;

                gettimeofday( &tv, NULL );

                ts.tv_sec = tv.tv_sec;
                ts.tv_nsec = 1000 * tv.tv_usec;
#endif /* defined( CLOCK_REALTIME ) */
                ts.tv_sec += Seconds;

                int rc =
                    pthread_cond_timedwait( &var, baton.pimpl_->ref( ), &ts );
                switch ( rc )
                {
                case 0:
                    // No error
                    break;
                case ETIMEDOUT:
                    break;
                default:
                {
                    std::ostringstream msg;

                    msg << "ConditionalVariable::TimedWait: system error: "
                        << ErrnoMessage( );
                    throw std::runtime_error( msg.str( ) );
                }
                break;
                }
            }

            inline void
            Wait( )
            {
                pthread_cond_wait( &var, baton.pimpl_->ref( ) );
            }

            cond_type     var;
            cv_baton_type baton;
        };

        ConditionalVariable::ConditionalVariable( ) : pimpl_( new impl( ) )
        {
        }

        ConditionalVariable::~ConditionalVariable( )
        {
        }

        MutexLock::baton_type
        ConditionalVariable::Mutex( )
        {
            return pimpl_->baton;
        }

        void
        ConditionalVariable::Broadcast( )
        {
            pimpl_->Broadcast( );
        }

        void
        ConditionalVariable::Release( )
        {
            pimpl_->baton.pimpl_->unlock( );
        }

        void
        ConditionalVariable::Signal( )
        {
            pimpl_->Signal( );
        }

        bool
        ConditionalVariable::TimedWait( INT_4U Seconds )
        {
            try
            {
                pimpl_->TimedWait( Seconds );
            }
            catch ( const impl::timeout_error& Exception )
            {
                return false;
            }
            catch ( const std::exception& Exception )
            {
                throw;
            }
            return false;
        }

        void
        ConditionalVariable::Wait( )
        {
            pimpl_->Wait( );
        }
    } // namespace AL
} // namespace LDASTools
