//
// 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
//

using LDASTools::AL::MutexLock;

void* check_mutex_thread( void* Args );

void
check_mutex( )
{
    int             status;
    pthread_t       handle;
    pthread_attr_t  attr;
    lock_check_info info;
    void*           res;

    info.err = (std::exception*)0;

    pthread_attr_init( &attr );
    status = pthread_create( &handle, &attr, check_mutex_thread, &info );
    if ( status != 0 )
    {
        throw std::runtime_error( "Unable to create thread" );
    }

    pthread_attr_init( &attr );
    pthread_join( handle, &res );
    if ( info.err )
    {
        if ( reinterpret_cast< MutexLock::BusyError* >( info.err ) )
        {
            delete info.err;
            info.err = (std::exception*)0;
            throw MutexLock::BusyError( );
        }
        delete info.err;
        info.err = (std::exception*)0;
    }
}

void*
check_mutex_thread( void* Args )
{
    lock_check_info* info = static_cast< lock_check_info* >( Args );

    try
    {
        mutex_baton.TryLock( __FILE__, __LINE__ );
        mutex_baton.Unlock( __FILE__, __LINE__ );
    }
    catch ( const MutexLock::BusyError& Exception )
    {
        info->err = new MutexLock::BusyError;
    }
    catch ( std::exception& Exception )
    {
        info->err = new std::exception( Exception );
    }
    return (void*)NULL;
}

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

static void
test_mutex_lock( )
{
    const char* leader = "MutexLock:";
    bool        status = true;

    {
        MutexLock l( mutex_baton, __FILE__, __LINE__ );

        try
        {
            status = false;
            check_mutex( );
        }
        catch ( const MutexLock::BusyError& Exception )
        {
            status = true;
        }
        catch ( const std::exception& Exception )
        {
            status = false;
        }
        Test.Check( status ) << leader << " Lock acquisition" << std::endl;
    }
    //---------------------------------------------------------------------
    // Verify that the lock is released as the locker goes out of scope
    //---------------------------------------------------------------------
    try
    {
        status = true;
        check_mutex( );
    }
    catch ( const std::exception& Exception )
    {
        status = false;
    }
    Test.Check( status ) << leader << " Lock release" << std::endl;
}

static void
test_mutex_lock_baton( )
{
    const char* leader = "MutexLock::baton_type:";

    bool status = true;
    ;

    //---------------------------------------------------------------------
    // Check the initial state
    //---------------------------------------------------------------------
    try
    {
        check_mutex( );
        status = true;
    }
    catch ( const std::exception& Exception )
    {
        status = false;
    }
    Test.Check( status )
        << leader
        << " Verify initially lock state of MutexLock::baton_type is unlocked"
        << std::endl;
    //---------------------------------------------------------------------
    // Verify that locking works
    //---------------------------------------------------------------------
    try
    {
        status = false;
        mutex_baton.Lock( __FILE__, __LINE__ );
        check_mutex( );
    }
    catch ( const MutexLock::BusyError& Exception )
    {
        status = true;
    }
    catch ( const std::exception& Exception )
    {
        status = false;
    }
    Test.Check( status ) << leader
                         << " Lock acquisition for MutexLock::baton_type"
                         << std::endl;
    //---------------------------------------------------------------------
    // Verify that unlocking works
    //---------------------------------------------------------------------
    try
    {
        status = true;
        mutex_baton.Unlock( __FILE__, __LINE__ );
        check_mutex( );
    }
    catch ( const std::exception& Exception )
    {
        status = false;
    }
    Test.Check( status ) << leader << " Lock release for MutexLock::baton_type"
                         << std::endl;
}

static void
test_mutex_lock_mix_and_match( )
{
    //---------------------------------------------------------------------
    // Case where the lock is:
    // 1) acquired by MutexLoc
    // 2) released by baton
    // 3) reacquired by baton
    // 4) release by scope
    //---------------------------------------------------------------------
    const char* leader = "MutexLock - Mix and Match:";
    bool        status = true;

    {
        MutexLock l( mutex_baton, __FILE__, __LINE__ );

        mutex_baton.Unlock( __FILE__, __LINE__ );
        try
        {
            status = true;
            check_mutex( );
        }
        catch ( const std::exception& Exception )
        {
            status = false;
        }
        Test.Check( status )
            << leader << " Lock acquired via mutex_baton" << std::endl;
        mutex_baton.Lock( __FILE__, __LINE__ );
        try
        {
            status = false;
            check_mutex( );
        }
        catch ( const MutexLock::BusyError& Exception )
        {
            status = true;
        }
        catch ( const std::exception& Exception )
        {
            status = false;
        }
        Test.Check( status )
            << leader << " Lock released via baton" << std::endl;
    }
    //---------------------------------------------------------------------
    // Verify that the lock is released as the locker goes out of scope
    //---------------------------------------------------------------------
    try
    {
        status = true;
        check_mutex( );
    }
    catch ( const std::exception& Exception )
    {
        status = false;
    }
    Test.Check( status ) << leader << " Lock still released once out of scope"
                         << std::endl;
}
