//
// 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>
#include <unistd.h>

#include <list>

#include "ldastoolsal/UnitTestTS.h"

#include "ldastoolsal/ErrorLog.hh"
#include "ldastoolsal/TaskTimer.hh"
#include "ldastoolsal/TaskThread.hh"
#include "ldastoolsal/ThreadPool.hh"
#include "ldastoolsal/Timeout.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/SignalHandler.hh"

LDASTools::Testing::UnitTestTS Test;

using LDASTools::AL::MutexLock;
using LDASTools::AL::SignalHandler;
using LDASTools::AL::StdErrLog;
using LDASTools::AL::TaskThread;
using LDASTools::AL::Timeout;
using LDASTools::AL::TimeoutException;
using LDASTools::AL::Timer;

typedef SignalHandler::signal_type signal_type;

static const signal_type WAITER_SIGNAL = SignalHandler::SIGNAL_TERMINATE;

struct sleeper_info_type
{
    pthread_t s_thread;
    int       s_sleep;
    int       s_wakeup;
};

typedef std::list< sleeper_info_type* > sleepers_type;

MutexLock::baton_type sleepers_key = MutexLock::Baton( );
sleepers_type         Sleepers;

static class Wakeup_ : public SignalHandler::Callback
{
public:
    typedef SignalHandler::signal_type signal_type;

    virtual void
    SignalCallback( signal_type Signal )
    {
    }
} wakeup;

void*
sleeper( void* Data )
{
    //---------------------------------------------------------------------
    // Setup the timer and wait for the results
    //---------------------------------------------------------------------
    sleeper_info_type* info = reinterpret_cast< sleeper_info_type* >( Data );
    Timer              nsleep( info->s_sleep, WAITER_SIGNAL );
    try
    {
        int status;
        Timeout( &nsleep, info->s_wakeup, status );
    }
    catch ( ... )
    {
    }

    //---------------------------------------------------------------------
    // Check the results
    //---------------------------------------------------------------------
    bool        status;
    std::string alarm;
    if ( info->s_sleep < info->s_wakeup )
    {
        alarm = "before";
        status = !nsleep.Error( );
    }
    else
    {
        alarm = "after";
        status = nsleep.Error( );
    }
    {
        Test.Lock( );
        Test.Check( status )
            << " Task completes " << alarm
            << " alarm rings: (task: " << info->s_sleep
            << " timeout: " << info->s_wakeup << ")" << std::endl;
        Test.UnLock( );
    }
    return (void*)NULL;
}

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

    retval->s_sleep = Sleep;
    retval->s_wakeup = Wakeup;
    {
        MutexLock l( sleepers_key, __FILE__, __LINE__ );

        Sleepers.push_back( retval );
        pthread_create( &( retval->s_thread ), NULL, sleeper, retval );
    }
}

void
task_thread_test( )
{
    //---------------------------------------------------------------------
    // Test the creation and destruction of the object
    //---------------------------------------------------------------------
    {
        TaskThread t;
        /* t.Mutex( );*/
    }
}

void
timer_test( bool Expire )
{
    Timer              nsleep( 10, WAITER_SIGNAL );
    std::ostringstream msg;
    bool               pass = false;

    try
    {
        int status;

        Timeout( &nsleep, ( Expire ) ? 5 : 15, status );
        pass = ( Expire == false );
    }
    catch ( const TimeoutException& te )
    {
        pass = ( Expire == true );
    }
    catch ( const std::exception& e )
    {
        pass = false;
        msg << "Unexpected exception: " << e.what( );
    }
    catch ( ... )
    {
        pass = false;
        msg << "Unknown exception";
    }
    Test.Lock( );
    Test.Check( pass ) << "timer_test: "
                       << ( ( Expire ) ? "Experation" : "Normal Completion" )
                       << msg.str( ) << std::endl;
    Test.UnLock( );
}

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

    SignalHandler::Register( &wakeup, WAITER_SIGNAL );
    //---------------------------------------------------------------------
    // Set to true for verbose output, false for no output from TimerThread
    //   class.
    //---------------------------------------------------------------------
    StdErrLog.IsOpen( false );

#if 0
  //---------------------------------------------------------------------
  // Test TaskThread class
  //---------------------------------------------------------------------

  task_thread_test( );
#endif /* 0 */

    //---------------------------------------------------------------------
    // Test that no exception is thrown when the task completes in
    //   a timely manner.
    //---------------------------------------------------------------------

    timer_test( false );

    //---------------------------------------------------------------------
    // Test that an exception is thrown when the timer expires
    //---------------------------------------------------------------------

    timer_test( true );

#if 0
  //---------------------------------------------------------------------
  // 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
  //---------------------------------------------------------------------
  {
    MutexLock	l( sleepers_key, __FILE__, __LINE__ );

    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( );
    }
  }
#endif /* 0 */
    //---------------------------------------------------------------------
    // Exit with the appropriate exit status
    //---------------------------------------------------------------------
    Test.Exit( );
    return 1;
}
