/*
 * Copyright (c) 2013-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <signal.h>

#include <tools_utils.h>

static int s_is_fired = 0;
static char *s_interrupt_message = NULL;

//siganls to handle
#ifdef __WIN__
static int signals_array[] =
{SIGINT};
#else
static int signals_array[] = {SIGINT, SIGQUIT, SIGTERM, SIGUSR1};
#endif

static void (*prev_handlers[sizeof(signals_array) / sizeof (signals_array[0])])(int sig);



             static void my_termination_handler(int sig)
{
    s_is_fired = sig; // assuming signals received are always different then zero
    if (s_interrupt_message) {
        fprintf(stderr, "%s", s_interrupt_message);
    }
}

#ifdef __WIN__
             static BOOL CtrlHandler( DWORD fdwCtrlType )
{
    switch (fdwCtrlType)
    {
    // Handle the CTRL-C signal.
    case CTRL_C_EVENT:
    // CTRL-CLOSE: confirm that the user wants to exit.
    case CTRL_CLOSE_EVENT:
    // Pass other signals to the next handler.
    case CTRL_BREAK_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT:
        my_termination_handler(SIGINT);
        return TRUE;

    default:
        return FALSE;
    }
}
#endif

             int  mft_signal_set_handling(int is_on)
{
    unsigned int i;

#ifdef __WIN__
    SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, is_on );
#endif
    static int first_on = 0;
    static int is_on_counter = 0;

    if (is_on_counter == 0 && is_on == 0) {
        //if we reach here it means previous handler is already set so there is no need to restore it once more.
        return 0;
    }
    // incr/decr counter
    if (is_on) {
        is_on_counter += 1;
    } else {
        is_on_counter = is_on_counter > 0 ? is_on_counter - 1 : is_on_counter;
    }

    if (is_on == 1 && first_on == 0) {
        /* first time we turned thin handler on so we save previous handlers in prev_handlers array */
        for (i = 0; i < sizeof(signals_array) / sizeof(signals_array[0]); i++) {
            prev_handlers[i] = signal(signals_array[i], my_termination_handler);
            if (prev_handlers[i] == SIG_ERR) {
                return -1;
            }
        }
        first_on = 1;
        return 0;
    }
    /* not the first time we turned on the signals */
    /* register term/kill signal handler */
    /* we need to call signal routine only when is_on_counter == 0 or 1 (more precisely when it goes from zero to one or visa versa )*/
    if ((is_on_counter == 0 && is_on == 0) || (is_on_counter == 1 && is_on == 1 )) {
        for (i = 0; i < sizeof(signals_array) / sizeof(signals_array[0]); i++) {
            if (signal(signals_array[i], is_on_counter > 0 ? my_termination_handler : prev_handlers[i]) == SIG_ERR) {
                return -1;
            }
        }
    }

    return 0;
}

             int  mft_signal_is_fired()
{
    return s_is_fired;
}

             void mft_signal_set_fired(int is_fired)
{
    s_is_fired = is_fired;
}

             void mft_signal_set_msg(char *msg)
{
    s_interrupt_message = msg;
}

             void mft_restore_and_raise()
{
    int sig;
    sig = mft_signal_is_fired();
    if (sig) {
        // reset received signal
        mft_signal_set_fired(0);
        // retore prev handler
        mft_signal_set_handling(0);
        //raise signal to let the previous handle deal with it.
        raise(sig);
    }
    mft_signal_set_handling(0);
    return;
}
