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

#include <iostream>
#include <typeinfo>

#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/IOLock.hh"
#include "ldastoolsal/SignalHandler.hh"
#include "ldastoolsal/Task.hh"
#include "ldastoolsal/TaskThread.hh"

#include "MutexLockBaton.cc"

using LDASTools::AL::MemChecker;
using LDASTools::AL::SignalHandler;

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

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

namespace LDASTools
{
    namespace AL
    {
        TaskThread::TaskThread( )
            : m_done( false ), m_task_available( ), m_task( (Task*)NULL ),
              m_task_type_name( (const char*)NULL ),
              m_cancel_method( Thread::CANCEL_UNKNOWN ),
              m_cancel_signal( SignalHandler::SIGNAL_UNKNOWN ),
              m_state( TASK_THREAD_NOT_STARTED )
        {
            CriticalSection( true );
            Spawn( );
            //-------------------------------------------------------------------
            // Helgrind may complain about a possible race condition here,
            //   but removing this wait will cause many segfaults since tasks
            //   are not properly initialized without it.
            //-------------------------------------------------------------------
            Wait( );
            CriticalSection( false );
        }

        TaskThread::~TaskThread( )
        {
            CriticalSection( true );

            switch ( m_state )
            {
            case TASK_THREAD_SLEEPING:
                m_done = true;
                m_task = (Task*)NULL;
                break;
            default:
                break;
            }

            m_task_available.Broadcast( );
            CriticalSection( false );

            CriticalSection( true );
            if ( m_state != TASK_THREAD_NOT_STARTED )
            {
                CriticalSection( false );
                Join( );
            }
            else
            {
                CriticalSection( false );
            }
        }

        void
        TaskThread::AddTask( Task* TaskToDo )
        {
            CriticalSection( true );
            //-------------------------------------------------------------------
            // Assign next task.
            //-------------------------------------------------------------------
            m_task = TaskToDo;
            //-------------------------------------------------------------------
            // Inform thread of the need to do work
            //-------------------------------------------------------------------
            m_task_available.Signal( );
            CriticalSection( false );
        }

        void
        TaskThread::CriticalSection( bool IsCriticalSection )
        {
            if ( IsCriticalSection )
            {
                Mutex( ).Lock( __FILE__, __LINE__ );
            }
            else
            {
                Mutex( ).Unlock( __FILE__, __LINE__ );
            }
        }

        std::string
        TaskThread::StateStr( enum state_type Value )
        {
            std::string retval;
            switch ( Value )
            {
            case TASK_THREAD_NOT_STARTED:
                retval = "NOT_STARTED";
                break;
            case TASK_THREAD_SLEEPING:
                retval = "SLEEPING";
                break;
            case TASK_THREAD_RUNNING:
                retval = "RUNNING";
                break;
            case TASK_THREAD_TIMEDOUT:
                retval = "TIMEOUT";
                break;
            case TASK_THREAD_FINISHED:
                retval = "FINISHED";
                break;
            case TASK_THREAD_EXITING:
                retval = "EXITING";
                break;
            }
            return retval;
        }

        void
        TaskThread::TimedWait( int Seconds )
        {
            CriticalSection( true );
            try
            {
                if ( m_task_available.TimedWait( Seconds ) == false )
                {
                    if ( m_state == TASK_THREAD_RUNNING )
                    {
                        m_state = TASK_THREAD_TIMEDOUT;
                    }
                }
            }
            catch ( ... )
            {
                //-----------------------------------------------------------------
                // Release the resource
                //-----------------------------------------------------------------
                CriticalSection( false );
                //-----------------------------------------------------------------
                // Continue with the exception
                //-----------------------------------------------------------------
                throw;
            }
            CriticalSection( false );
        }

        bool
        TaskThread::Halt( )
        {
            CriticalSection( true );
            bool retval = true;
            if ( ( m_state == TASK_THREAD_RUNNING ) ||
                 ( m_state == TASK_THREAD_TIMEDOUT ) )
            {
                switch ( m_cancel_method )
                {
                case Thread::CANCEL_UNKNOWN:
                    // Task never got started in loop
                    break;
                case Thread::CANCEL_ABANDON:
                    if ( m_state == TASK_THREAD_TIMEDOUT )
                    {
                        // The task took too long. It is time to orphan the
                        // thread so
                        //   it will complete in the backgroiund
                        m_done = true;
                        retval = false;
                        Detach( );
                    }
                    break;
                case Thread::CANCEL_BY_SIGNAL:
                    //---------------------------------------------------------------
                    // Cancellation model
                    //---------------------------------------------------------------
                    if ( m_cancel_signal == SignalHandler::SIGNAL_UNKNOWN )
                    {
                        Cancel( );
                    }
                    else
                    {
                        bool timedout( m_state == TASK_THREAD_TIMEDOUT );
                        //-------------------------------------------------------------
                        // Signal model
                        //-------------------------------------------------------------
                        Kill( m_cancel_signal );
                        if ( timedout )
                        {
                            Wait( );
                        }
                        CriticalSection( false );
                        return retval;
                    }
                    break;
                default:
                    Thread::Cancel( );
                    break;
                } // switch
            }
            if ( retval && ( m_state == TASK_THREAD_TIMEDOUT ) )
            {
                Wait( );
            }
            CriticalSection( false );
            return retval;
        }

        void
        TaskThread::Wait( )
        {
            m_task_available.Wait( );
        }

        /// This method does the work of the thread and runs
        /// in the conrtolled thread.
        /// It does a task and then waits for another task.
        /// This allows for the thread to be reused.
        void
        TaskThread::action( )
        {
            for ( ;; )
            {
                CriticalSection( true );

                if ( MemChecker::IsExiting( ) == true )
                {
                    m_done = true;
                }
                if ( m_state == TASK_THREAD_NOT_STARTED )
                {
                    m_state = TASK_THREAD_SLEEPING;
                    m_task_available.Signal( );
                    CriticalSection( false );
                    continue;
                }

                m_state = TASK_THREAD_SLEEPING;
                while ( ( m_task == (Task*)NULL ) && ( !m_done ) )
                {
                    //---------------------------------------------------------------
                    // Waiting for something to do
                    //---------------------------------------------------------------
                    CancellationType( CANCEL_ASYNCHRONOUS );
                    m_task_available.Wait( );
                    CancellationType( CANCEL_IGNORE );
                }
                if ( m_done )
                {
                    //---------------------------------------------------------------
                    // There is no more work for this thread. It is time to
                    //   shut down the thread. Before doing so, release critical
                    //   resources.
                    //---------------------------------------------------------------
                    m_state = TASK_THREAD_EXITING;
                    m_task_available.Signal( );
                    CriticalSection( false );
                    break;
                }
                if ( m_task == (Task*)NULL )
                {
                    continue;
                }
                //-----------------------------------------------------------------
                // Some task is being requested.
                //-----------------------------------------------------------------
                m_state = TASK_THREAD_RUNNING;
                Task* volatile t = m_task;

                if ( t )
                {
                    m_cancel_method = t->CancelMethod( );
                    m_cancel_signal = t->CancelSignal( );
                    const Task& tname = *t;
                    m_task_type_name = typeid( tname ).name( );
                    task_name = t->TaskName( );
                }

                sigset_t old_signal_set;

                if ( m_cancel_signal != SignalHandler::SIGNAL_UNKNOWN )
                {
                    sigset_t signal_set;

                    sigemptyset( &signal_set );
                    sigaddset( &signal_set,
                               SignalHandler::OSSignal( m_cancel_signal ) );

                    //---------------------------------------------------------------
                    // Setup to handle the signal
                    //---------------------------------------------------------------
                    SignalHandler::Register( &wakeup, m_cancel_signal );
                    //---------------------------------------------------------------
                    // Preserve the old signal mask
                    //---------------------------------------------------------------
                    pthread_sigmask( SIG_SETMASK, NULL, &old_signal_set );
                    //---------------------------------------------------------------
                    // Set the new signal mask
                    //---------------------------------------------------------------
                    pthread_sigmask( SIG_UNBLOCK, &signal_set, NULL );
                }
                //-----------------------------------------------------------------
                // Perform the request
                //-----------------------------------------------------------------
                if ( t )
                {
                    try
                    {
                        CriticalSection( false );
                        ( *t )( );
                        CriticalSection( true );
                    }
                    catch ( const std::exception& E )
                    {
                        CriticalSection( true );
                        if ( IsCancelled( ) )
                        {
                            Thread::signal_type sig;

                            switch ( CancellationType( sig ) )
                            {
                            case CANCEL_ASYNCHRONOUS:
                            case CANCEL_DEFERRED:
                                CriticalSection( false );
                                throw; // Rethrow the cancelation exception
                                break;
                            default:
                                break;
                            }
                        }
                        //------------------------------------------------------------
                        // Ignore any exception trown by the thread since there
                        //   is nothing that can be done about it.
                        // :TODO: capture and rethrow exceptions.
                        //------------------------------------------------------------
                    }
                }

                if ( m_state == TASK_THREAD_RUNNING )
                {
                    //---------------------------------------------------------------
                    // Normal completion
                    //---------------------------------------------------------------
                    m_state = TASK_THREAD_FINISHED;
                }

                if ( m_cancel_signal != SignalHandler::SIGNAL_UNKNOWN )
                {
                    //---------------------------------------------------------------
                    // Restore the signal mask
                    //---------------------------------------------------------------
                    pthread_sigmask( SIG_SETMASK, &old_signal_set, NULL );
                }
                //-----------------------------------------------------------------
                // Call Task Completion code.
                //-----------------------------------------------------------------
                if ( t )
                {
                    t->OnCompletion( m_state );
                    if ( t->DeleteOnCompletion( ) )
                    {
                        //-------------------------------------------------------------
                        // Caller has entrusted us to clean up the memory.
                        //-------------------------------------------------------------
                        delete t;
                    }
                }
                m_cancel_method = Thread::CANCEL_UNKNOWN;
                if ( m_task == t )
                {
                    m_task = (Task*)NULL;
                }
                m_task_available.Signal( );
                CriticalSection( false );
            }
        }
    } // namespace AL
} // namespace LDASTools
