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

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include <ldastoolsal_config.h>

#include <list>

#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/TaskThread.hh"
#include "ldastoolsal/ThreadPool.hh"

using LDASTools::AL::MemChecker;
using LDASTools::AL::MutexLock;
using LDASTools::AL::TaskThread;
using LDASTools::AL::ThreadPool;

typedef std::list< TaskThread* > thread_container_type;

static bool at_exit_initialized = false;

static void on_exit( );

//-----------------------------------------------------------------------
/// \brief Obtain lock
///
/// The use of a function ensures the proper initialization of the data
/// without having to depend on the link order initialization.
//-----------------------------------------------------------------------
inline MutexLock::baton_type
baton( )
{
    static MutexLock::baton_type baton;

    return baton;
}

//-----------------------------------------------------------------------
/// \brief Obtain the queue of available resources
///
/// The use of a function ensures the proper initialization of the data
/// without having to depend on the link order initialization.
//-----------------------------------------------------------------------
inline static thread_container_type&
available( )
{
    static thread_container_type retval;

    return retval;
}

//-----------------------------------------------------------------------
/// \brief Obtain the queue of resources currently in use.
///
/// The use of a function ensures the proper initialization of the data
/// without having to depend on the link order initialization.
//-----------------------------------------------------------------------
inline static thread_container_type&
in_use( )
{
    static thread_container_type retval;

    return retval;
}

//-----------------------------------------------------------------------
/// \brief Obtain the queue of resources in limbo
///
/// The use of a function ensures the proper initialization of the data
/// without having to depend on the link order initialization.
//-----------------------------------------------------------------------
#if USE_LIMBO
inline static thread_container_type&
limbo( )
{
    static thread_container_type retval;

    return retval;
}

inline bool
limbo_pop( )
{
    bool retval = false;

    //---------------------------------------------------------------------
    // Reuse
    //---------------------------------------------------------------------

    //---------------------------------------------------------------------
    // Looping over all entries to find if any for which the thread
    //   has completed.
    //---------------------------------------------------------------------
    for ( thread_container_type::iterator cur = limbo( ).begin( ),
                                          last = limbo( ).end( );
          cur != last;
          ++cur )
    {
        if ( !( *cur )->IsAlive( ) )
        {
            in_use( ).push_front( *cur );
            cur = limbo( ).erase( cur );
            retval = true;
            break;
        }
    }
    return ( retval );
}
#endif /* USE_LIMBO */

namespace LDASTools
{
    namespace AL
    {
        //-------------------------------------------------------------------
        /// Secure a resource from the pool of resouces.
        /// If there is no resouce available at the time of the call,
        /// a new resouce is allocated and returned to the caller.
        //-------------------------------------------------------------------
        TaskThread*
        ThreadPool::Acquire( )
        {
            if ( MemChecker::IsExiting( ) )
            {
                //---------------------------------------------------------------
                // Do not give out any more threads if the application is
                // in the process of shutting down.
                //---------------------------------------------------------------
                return (TaskThread*)NULL;
            }
            TaskThread* retval = (TaskThread*)NULL;

            {
                MutexLock lock( baton( ), __FILE__, __LINE__ );

                if ( at_exit_initialized == false )
                {
                    MemChecker::Append( on_exit, "ThreadPool::on_exit", 150 );
                    at_exit_initialized = true;
                }

                if ( available( ).empty( ) == false )
                {
                    //-------------------------------------------------------------
                    // Reuse
                    //-------------------------------------------------------------
                    in_use( ).push_front( available( ).front( ) );
                    available( ).pop_front( );
                }
#if USE_LIMBO
                else if ( limbo_pop( ) )
                {
                }
#endif /* USE_LIMBO */
                else
                {
                    //-------------------------------------------------------------
                    // Before creating a new entry, make sure the in_use queue
                    // has been purged of all threads that have been abandoned
                    // and are now complete
                    //-------------------------------------------------------------
                    thread_container_type::iterator cur = in_use( ).begin( );
                    while ( cur != in_use( ).end( ) )
                    {
                        if ( ( ( *cur )->State( ) ==
                               TaskThread::TASK_THREAD_EXITING ) &&
                             ( ( *cur )->IsAlive( ) == false ) )
                        {
                            if ( ( *cur )->IsDetached( ) )
                            {
                                cur = in_use( ).erase( cur );
                            }
                            else
                            {
                                TaskThread* t( *cur );

                                in_use( ).erase( cur );
                                available( ).push_back( t );
                            }
                        }
                        else
                        {
                            ++cur;
                        }
                    }
                    if ( available( ).empty( ) == false )
                    {
                        //-----------------------------------------------------------
                        // Reuse something that was just reclaimed
                        //-----------------------------------------------------------
                        in_use( ).push_front( available( ).front( ) );
                        available( ).pop_front( );
                    }
                    else
                    {
                        //-----------------------------------------------------------
                        // Create a new instance
                        //-----------------------------------------------------------
                        in_use( ).push_front( new TaskThread );
                    }
                }
                retval = in_use( ).front( );
            }

            return retval;
        }

        //-------------------------------------------------------------------
        /// Check for Resource
        //-------------------------------------------------------------------
        bool
        ThreadPool::Active( TaskThread* Resource )
        {
            bool retval = false;

            if ( Resource )
            {
                MutexLock lock( baton( ), __FILE__, __LINE__ );

                for ( thread_container_type::const_iterator
                          cur = in_use( ).begin( ),
                          last = in_use( ).end( );
                      cur != last;
                      ++cur )
                {
                    if ( *cur == Resource )
                    {
                        retval = true;
                        break;
                    }
                }
            }
            return retval;
        }

        //-------------------------------------------------------------------
        /// Loop over each active thread in a thread safe manor
        //-------------------------------------------------------------------
        void
        ThreadPool::ForEach( UnaryFunction& Func )
        {
            MutexLock lock( baton( ), __FILE__, __LINE__ );

            for ( thread_container_type::const_iterator
                      cur = in_use( ).begin( ),
                      last = in_use( ).end( );
                  cur != last;
                  ++cur )
            {
                Func( *( *cur ), UnaryFunction::THREAD_POOL_TASK_ACTIVE );
            }
            for ( thread_container_type::const_iterator
                      cur = available( ).begin( ),
                      last = available( ).end( );
                  cur != last;
                  ++cur )
            {
                Func( *( *cur ), UnaryFunction::THREAD_POOL_TASK_IDLE );
            }
        }

        //-------------------------------------------------------------------
        /// Recyle the resource.
        //-------------------------------------------------------------------
        void
        ThreadPool::Relinquish( TaskThread* Resource )
        {
            if ( Resource )
            {
                bool exiting = true;
                {
                    MutexLock lock( baton( ), __FILE__, __LINE__ );

                    in_use( ).remove( Resource );
                    if ( MemChecker::IsExiting( ) == false )
                    {
#if USE_LIMBO
                        if ( !Resource->IsParentThread( ) &&
                             Resource->IsAlive( ) )
                        {
                            //---------------------------------------------------------
                            // Child thread is still running so put into limbo
                            //---------------------------------------------------------
                            limbo( ).push_back( Resource );
                        }
                        else
                        {
                            //---------------------------------------------------------
                            // Thread completed, so it is immediately available
                            //   for use.
                            //---------------------------------------------------------
                            available( ).push_back( Resource );
                        }
#else /* USE_LIMBO */
                        available( ).push_back( Resource );
#endif /* USE_LIMBO */
                        exiting = false;
                    }
                }
                if ( exiting )
                {
                    // delete Resource;
                }
            }
        }

        //-------------------------------------------------------------------
        /// \note
        ///     This method should not be called by others.
        ///     It is provided to help facilitate cleanup at program exit.
        //-------------------------------------------------------------------
        void
        ThreadPool::Reset( )
        {
            MutexLock lock( baton( ), __FILE__, __LINE__ );
            //-----------------------------------------------------------------
            /// Get rid of threads currently doing something interesting
            //-----------------------------------------------------------------
            for ( thread_container_type::const_iterator
                      cur = in_use( ).begin( ),
                      last = in_use( ).end( );
                  cur != last;
                  ++cur )
            {
                delete ( *cur );
            }
            in_use( ).erase( in_use( ).begin( ), in_use( ).end( ) );
            //-----------------------------------------------------------------
            /// Get rid of threads waiting to do something interesting
            //-----------------------------------------------------------------
            for ( thread_container_type::const_iterator
                      cur = available( ).begin( ),
                      last = available( ).end( );
                  cur != last;
                  ++cur )
            {
                delete ( *cur );
            }
            available( ).erase( available( ).begin( ), available( ).end( ) );
        }
    } // namespace AL

} // namespace LDASTools

static void
on_exit( )
{
    {
        MutexLock lock( baton( ), __FILE__, __LINE__ );

        for ( thread_container_type::iterator cur = in_use( ).begin( ),
                                              last = in_use( ).end( );
              cur != last;
              ++cur )
        {
            if ( *cur )
            {
                ( *cur )->Halt( );
            }
        }
    }
    while ( in_use( ).empty( ) == false )
    {
        //-------------------------------------------------------------------
        // Give time for tasks to complete
        //-------------------------------------------------------------------
        {
            for ( thread_container_type::iterator cur = in_use( ).begin( ),
                                                  last = in_use( ).end( );
                  cur != last; )
            {
#if 0
	std::cerr << "DEBUG: typeid: " << typeid( *cur ).name( )
		  << " ptr: " << (void*)(*cur)
		  << " name: " << ( ( (*cur)->Name( ) )
				    ? (*cur)->Name( )
				    : "unknown" )
		  << " IsAlive: " << ( (*cur)->IsAlive( )
				       ? "true" : "false" )
		  << " state: " << (*cur)->State( )
		  << " CancellationType: " << (*cur)->CancellationType( )
	  
		  << std::endl
	  ;
#endif /* 0 */
                if ( ( ( *cur )->IsAlive( ) == true ) &&
                     ( *cur )->State( ) != TaskThread::TASK_THREAD_EXITING )
                {
                    ( *cur )->Halt( );
                }
                if ( ( *cur )->IsAlive( ) == false )
                {
                    ( *cur )->Join( );
                    cur = in_use( ).erase( cur );
                    continue;
                }
                ++cur;
            }
        }
        struct timespec duration;

        duration.tv_sec = 5;
        duration.tv_nsec = 0;

        nanosleep( &duration, NULL );
    }
    {
        struct timespec duration;

        duration.tv_sec = 10;
        duration.tv_nsec = 0;

        nanosleep( &duration, NULL );
    }
    ThreadPool::Reset( );
}
