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

#include <map>
#include <unordered_map>

#include "SignalHandler.hh"

SINGLETON_INSTANCE_DEFINITION( SingletonHolder< LDASTools::AL::SignalHandler > )

typedef int os_signal_type;

HASH_NAMESPACE_BEGIN
{
    template <>
    struct hash< LDASTools::AL::SignalHandler::signal_type >
    {
        size_t
        operator( )( LDASTools::AL::SignalHandler::signal_type Key ) const
        {
            return static_cast< size_t >( Key );
        }
    };
}
HASH_NAMESPACE_END

namespace
{
    static const os_signal_type OS_SIGNAL_UNKNOWN = 0;

    os_signal_type
    os_signal( LDASTools::AL::SignalHandler::signal_type Signal );

    LDASTools::AL::SignalHandler::signal_type
    os_reverse_signal( os_signal_type Signal );

} // namespace

extern "C" {
void
handler( int Signal )
{
    LDASTools::AL::SignalHandler::Callbacks( os_reverse_signal( Signal ) );
}
}

namespace LDASTools
{
    namespace AL
    {
        SignalHandler::Callback::~Callback( )
        {
        }

        SignalHandler::IgnoreCallback::~IgnoreCallback( )
        {
        }

        void
        SignalHandler::IgnoreCallback::SignalCallback( signal_type Signal )
        {
            // Do nothing with the signal
        }

        //------------------------------------------------------------------------
        //------------------------------------------------------------------------

        SignalHandler::IgnoreCallback SignalHandler::IgnoreCB;

        void
        SignalHandler::Callbacks( signal_type Signal )
        {
            Instance( ).callbacks( Signal );
        }

        int
        SignalHandler::OSSignal( signal_type Signal )
        {
            return os_signal( Signal );
        }

        void
        SignalHandler::Register( Callback* CB, signal_type Signal )
        {
            Instance( ).reg( CB, Signal );
        }

        void
        SignalHandler::ThreadCaptureSignal( signal_type Signal )
        {
            //-----------------------------------------------------------------
            // Setup to cancel via signal.
            //-----------------------------------------------------------------
            sigset_t signal_set;
            sigset_t old_signal_set;

            sigemptyset( &signal_set );
            sigaddset( &signal_set, os_signal( 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 );
        }

        void
        SignalHandler::ThreadIgnoreSignal( signal_type Signal )
        {
            //-----------------------------------------------------------------
            // Setup to cancel via signal.
            //-----------------------------------------------------------------
            sigset_t signal_set;
            sigset_t old_signal_set;

            sigemptyset( &signal_set );
            sigaddset( &signal_set, os_signal( Signal ) );

            //-----------------------------------------------------------------
            // Preserve the old signal mask
            //-----------------------------------------------------------------
            pthread_sigmask( SIG_SETMASK, NULL, &old_signal_set );
            //-----------------------------------------------------------------
            // Set the new signal mask
            //-----------------------------------------------------------------
            pthread_sigmask( SIG_BLOCK, &signal_set, NULL );
        }

        void
        SignalHandler::Unregister( Callback* CB, signal_type Signal )
        {
            Instance( ).unreg( CB, Signal );
        }

        void
        SignalHandler::callbacks( signal_type Signal )
        {
            callbacks_type& callbacks = signal_queue[ Signal ];

            for ( callbacks_type::iterator cur = callbacks.begin( ),
                                           last = callbacks.end( );
                  cur != last;
                  ++cur )
            {
                ( *cur )->SignalCallback( Signal );
            }
        }

        //-------------------------------------------------------------------
        /// Register a callback
        //-------------------------------------------------------------------
        void
        SignalHandler::reg( Callback* CB, signal_type Signal )
        {
            callbacks_type& callbacks = signal_queue[ Signal ];

            if ( callbacks.size( ) == 0 )
            {
                //---------------------------------------------------------------
                // Need to register handler since there is there will be
                // something to handle
                /// \todo
                ///     Need to be able to store the handler retrieved in sa_old
                ///     so when this signal handler is unloaded, the previous
                ///     handler can be restored.
                //---------------------------------------------------------------
                struct sigaction sa;
                struct sigaction sa_old;

                sa.sa_handler = handler;
                sigemptyset( &sa.sa_mask );
                // sa.sa_flags = SA_RESTART;
                sa.sa_flags = 0;

                if ( sigaction( os_signal( Signal ), &sa, &sa_old ) == -1 )
                {
                    std::cerr << "Unable to establish signal handler"
                              << std::endl;
                }
            }
            for ( callbacks_type::iterator cur = callbacks.begin( ),
                                           last = callbacks.end( );
                  cur != last;
                  ++cur )
            {
                if ( *cur == CB )
                {
                    //-------------------------------------------------------------
                    // Already in the queue
                    //-------------------------------------------------------------
                    return;
                }
            }
            //-----------------------------------------------------------------
            // Add to list
            //-----------------------------------------------------------------
            callbacks.push_back( CB );
        }

        //-------------------------------------------------------------------
        /// Unregister a callback
        //-------------------------------------------------------------------
        void
        SignalHandler::unreg( Callback* CB, signal_type Signal )
        {
            callbacks_type& callbacks = signal_queue[ Signal ];

            for ( callbacks_type::iterator cur = callbacks.begin( ),
                                           last = callbacks.end( );
                  cur != last;
                  ++cur )
            {
                if ( *cur == CB )
                {
                    //-------------------------------------------------------------
                    // Found the callback. Remove it and break out of the loop.
                    //-------------------------------------------------------------
                    callbacks.erase( cur );
                    break;
                }
            }
            if ( callbacks.size( ) == 0 )
            {
                //---------------------------------------------------------------
                // Need to remove the signal handler since there is no
                // reason to keep processing
                //---------------------------------------------------------------
                struct sigaction sa;

                sa.sa_handler = SIG_DFL;
                sigemptyset( &sa.sa_mask );
                sa.sa_flags = SA_RESTART;

                if ( sigaction( os_signal( Signal ), &sa, NULL ) == -1 )
                {
                    std::cerr << "Unable to establish signal handler"
                              << std::endl;
                }
            }
        }

    } // namespace AL
} // namespace LDASTools

namespace
{
    using LDASTools::AL::SignalHandler;

    typedef std::map< SignalHandler::signal_type, os_signal_type >
        os_signal_map_type;

    typedef std::map< os_signal_type, SignalHandler::signal_type >
        os_reverse_signal_map_type;

    static const os_signal_map_type os_signal_map = {
        { SignalHandler::SIGNAL_ABORT, SIGABRT },
        { SignalHandler::SIGNAL_ALARM, SIGALRM },
        { SignalHandler::SIGNAL_HANGUP, SIGHUP },
        { SignalHandler::SIGNAL_ISALIVE, 0 },
        { SignalHandler::SIGNAL_TERMINATE, SIGTERM },
        { SignalHandler::SIGNAL_USER_1, SIGUSR1 },
        { SignalHandler::SIGNAL_USER_2, SIGUSR2 }
    };

    static const os_reverse_signal_map_type os_reverse_signal_map = {
#define m_set_sig_( sig )                                                      \
    {                                                                          \
        os_signal( sig ), sig                                                  \
    }
        m_set_sig_( SignalHandler::SIGNAL_ABORT ),
        m_set_sig_( SignalHandler::SIGNAL_ALARM ),
        m_set_sig_( SignalHandler::SIGNAL_HANGUP ),
        m_set_sig_( SignalHandler::SIGNAL_ISALIVE ),
        m_set_sig_( SignalHandler::SIGNAL_TERMINATE ),
        m_set_sig_( SignalHandler::SIGNAL_USER_1 ),
        m_set_sig_( SignalHandler::SIGNAL_USER_2 )

#undef m_set_sig_
    };
    os_signal_type
    os_signal( SignalHandler::signal_type Signal )
    {
        os_signal_map_type::const_iterator pos = os_signal_map.find( Signal );
        if ( pos == os_signal_map.end( ) )
        {
            return OS_SIGNAL_UNKNOWN;
        }
        return pos->second;
    }

    SignalHandler::signal_type
    os_reverse_signal( os_signal_type Signal )
    {
        os_reverse_signal_map_type::const_iterator pos = os_reverse_signal_map.find( Signal );
        if ( pos == os_reverse_signal_map.end( ) )
        {
            return SignalHandler::SIGNAL_UNKNOWN;
        }
        return pos->second;
    }
} // namespace
