//
// 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 <stdlib.h>
#include <string.h>
#include <iostream>
#include <list>
#include <map>

#if HAVE_VALGRIND_VALGRIND_H
#include "valgrind/valgrind.h"
#endif /* HAVE_VALGRIND_VALGRIND_H */

#include "ldastoolsal/AtExit.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/ReadWriteLock.hh"

#include "MallocAllocator.hh"

#ifdef _MSC_VER // Check if MSVC is being used
#pragma warning( push )
#pragma warning( disable : 4996 ) // Disable deprecated warnings in MSVC
#elif defined( __GNUC__ ) ||                                                   \
    defined( __clang__ ) // Check if GCC or Clang is being used
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored                                                 \
    "-Wdeprecated-declarations" // Disable deprecated warnings in GCC/Clang
#endif

namespace anonymous
{
    void exit_handler( );
}

// SINGLETON_INSTANCE_DEFINITION(SingletonHolder<AtExit>)

namespace
{
    using LDASTools::AL::AtExit;
    using LDASTools::AL::MutexLock;
    using LDASTools::AL::ReadWriteLock;

#if 0
  static void	cleanup( );
#endif /* 0 */

    template < class T >
    inline bool
    operator==( const malloc_allocator< T >&, const malloc_allocator< T >& )
    {
        return true;
    }

    template < class T >
    inline bool
    operator!=( const malloc_allocator< T >&, const malloc_allocator< T >& )
    {
        return false;
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------
    class ExitQueueNode
    {
    public:
        ~ExitQueueNode( )
        {
            while ( data_container.size( ) > 0 )
            {
                data_container.back( ).purge( );
                data_container.pop_back( );
            }
        }

        void
        Append( AtExit::ExitFunction Function, const char* Name )
        {
#if defined( __VALGRIND_MAJOR__ )
            if ( RUNNING_ON_VALGRIND )
            {
                data_container.push_back( data_type( Function, Name ) );
            }
#endif /* defined( __VALGRIND_MAJOR__ ) */
        }

    private:
        class func_node
        {
        public:
            typedef AtExit::ExitFunction function_type;

            func_node( function_type Function, const char* Name )
                : function( Function ), name( ::strdup( Name ) )
            {
            }

            void
            purge( )
            {
                if ( function )
                {
                    ( *function )( );
                    function = (function_type)NULL;
                }
                if ( name )
                {
                    ::free( const_cast< char* >( name ) );
                    name = (const char*)NULL;
                }
            }

        private:
            function_type function;
            const char*   name;
        };

        typedef func_node data_type;
        typedef std::list< data_type, malloc_allocator< data_type > >
            data_container_type;

        data_container_type data_container;
    };

    class ExitQueue
    {
    public:
        ExitQueue( )
            : is_exiting_baton( /*__FILE__, __LINE__,*/ false ),
              exit_queue_baton( __FILE__, __LINE__, false )
        {
        }

        inline void
        Append( AtExit::ExitFunction Function, const char* Name, int Ring )
        {
            MutexLock l( exit_queue_baton, __FILE__, __LINE__ );

            if ( IsExiting( ) )
            {
                ( *Function )( );
                return;
            }
            exit_queue[ Ring ].Append( Function, Name );
        }

        inline void
        Cleanup( )
        {
            MutexLock l( exit_queue_baton, __FILE__, __LINE__ );

            bool state;

            state = IsExiting( true );

            while ( exit_queue.size( ) > 0 )
            {
                exit_queue_type::iterator cur = exit_queue.end( );
                --cur;
                exit_queue.erase( cur );
            }

            IsExiting( state );
        }

        inline bool
        IsExiting( ) const
        {
            ReadWriteLock l(
                is_exiting_baton, ReadWriteLock::READ, __FILE__, __LINE__ );

            return is_exiting;
        }

        inline bool
        IsExiting( bool Value )
        {
            ReadWriteLock l(
                is_exiting_baton, ReadWriteLock::WRITE, __FILE__, __LINE__ );

            bool retval = is_exiting;

            is_exiting = Value;

            return retval;
        }

    private:
        typedef std::map<
            int,
            ExitQueueNode,
            std::less< int >,
            malloc_allocator< std::pair< const int, ExitQueueNode > > >
            exit_queue_type;

        bool                      is_exiting;
        ReadWriteLock::baton_type is_exiting_baton;

        exit_queue_type       exit_queue;
        MutexLock::baton_type exit_queue_baton;
    };

    ExitQueue&
    exit_queue( )
    {
        static ExitQueue eq;

        return eq;
    }

#if 0
  void
  cleanup( )
  {
    exit_queue( ).IsExiting( true );

#if 0
    AtExit::Cleanup( );
#endif /* 0 */
  }
#endif /* 0 */

} // namespace

namespace LDASTools
{
    namespace AL
    {
        void
        AtExit::Append( AtExit::ExitFunction Function,
                        const std::string&   Name,
                        int                  Ring )
        {
            exit_queue( ).Append( Function, Name.c_str( ), Ring );
        }

        void
        AtExit::Cleanup( )
        {
            exit_queue( ).Cleanup( );
        }

        bool
        AtExit::IsExiting( )
        {
            return exit_queue( ).IsExiting( );
        }
    } // namespace AL
} // namespace LDASTools

#ifdef _MSC_VER
#pragma warning( pop ) // Restore MSVC warning state
#elif defined( __GNUC__ ) || defined( __clang__ )
#pragma GCC diagnostic pop // Restore GCC/Clang warning state
#endif
