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

#include <list>

#include "ldastoolsal/ErrorLog.hh"
#include "ldastoolsal/unittest.h"
#include "ldastoolsal/TimerThread.hh"

LDASTools::AL::UnitTest Test;
using LDASTools::AL::TimerThread;

#define WAKEUP_SIGNAL SIGUSR1

struct sleeper_info_type
{
    pthread_t          s_thread;
    int                s_sleep;
    int                s_wakeup;
    TimerThread::key_t s_key;
};

typedef std::list< sleeper_info_type* > sleepers_type;

sleepers_type Sleepers;

void
wakeup( int )
{
}

void*
sleeper( void* Data )
{
    struct timespec req;
    struct timespec rem;

    //---------------------------------------------------------------------
    // Setup the timer and wait for the results
    //---------------------------------------------------------------------
    sleeper_info_type* info = reinterpret_cast< sleeper_info_type* >( Data );
    info->s_key = TimerThread::AddTimer( info->s_wakeup, WAKEUP_SIGNAL );
    req.tv_sec = info->s_sleep;
    req.tv_nsec = 0;
    int retval = nanosleep( &req, &rem );

    //---------------------------------------------------------------------
    // Remove the timer
    //---------------------------------------------------------------------
    bool thread_removed = TimerThread::RemoveTimer( info->s_key );

    //---------------------------------------------------------------------
    // Check the results
    //---------------------------------------------------------------------
    if ( info->s_sleep < info->s_wakeup )
    {
        Test.Check( retval == 0 )
            << "[Key: " << info->s_key << "]"
            << " Wakeup before alarm rings: (sleep: " << info->s_sleep
            << " wakeup: " << info->s_wakeup << ")" << std::endl;
        Test.Check( thread_removed )
            << "[Key: " << info->s_key << "]"
            << " Removed alarm from queue" << std::endl;
    }
    else
    {
        Test.Check( retval == -1 )
            << "[Key: " << info->s_key << "]"
            << " Wakeup after alarm rings: (sleep: " << info->s_sleep
            << " wakeup: " << info->s_wakeup << ")" << std::endl;
        Test.Check( !thread_removed )
            << "[Key: " << info->s_key << "]"
            << " Alarm removed from queue" << std::endl;
    }
    return (void*)NULL;
}

//-----------------------------------------------------------------------
// Wrapper function to start the thread
//-----------------------------------------------------------------------
void
sleeper_test( int Sleep, int Wakeup )
{
    Test.Message( ) << "Setting up sleeper_test: Sleep: " << Sleep
                    << " Wakeup: " << Wakeup << std::endl;
    sleeper_info_type* retval = new sleeper_info_type;

    retval->s_sleep = Sleep;
    retval->s_wakeup = Wakeup;
    Sleepers.push_back( retval );
    pthread_create( &( retval->s_thread ), NULL, sleeper, retval );
}

int
main( int ArgC, char** ArgV )
{
    //---------------------------------------------------------------------
    // Initialize the test structure
    //---------------------------------------------------------------------
    Test.Init( ArgC, ArgV );

    //---------------------------------------------------------------------
    // Set to true for verbose output, false for no output from TimerThread
    //   class.
    //---------------------------------------------------------------------
    LDASTools::AL::StdErrLog.IsOpen( false );
    //---------------------------------------------------------------------
    // Create the instance
    //---------------------------------------------------------------------
    TimerThread::Instance( );
    signal( WAKEUP_SIGNAL, wakeup );

    //---------------------------------------------------------------------
    // Setup and run the differnet threads
    //---------------------------------------------------------------------

    sleeper_test( 5, 10 );
    sleeper_test( 10, 5 );
    sleeper_test( 20, 18 );
    sleeper_test( 10, 20 );
    sleep( 30 );
    sleeper_test( 5, 10 );
    sleeper_test( 10, 5 );
    sleeper_test( 20, 18 );
    sleeper_test( 10, 20 );

    //---------------------------------------------------------------------
    // Wait for all threads to terminate
    //---------------------------------------------------------------------
    while ( Sleepers.size( ) > 0 )
    {
        //-------------------------------------------------------------------
        // Get rid of any threads that did not really start
        //-------------------------------------------------------------------
        if ( Sleepers.front( )->s_thread == (pthread_t)NULL )
        {
            delete Sleepers.front( );
            Sleepers.pop_front( );
            continue;
        }
        //-------------------------------------------------------------------
        // Join threads that started for real
        //-------------------------------------------------------------------
        pthread_join( Sleepers.front( )->s_thread, NULL );
        delete Sleepers.front( );
        Sleepers.pop_front( );
    }

    //---------------------------------------------------------------------
    // Exit with the appropriate exit status
    //---------------------------------------------------------------------
    Test.Exit( );
    return 1;
}
