#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

#if defined(__APPLE__)
typedef sig_t signalhandler_type;
#else
typedef sighandler_t signalhandler_type;
#endif

extern "C" {
    void interrupt_init_priv( bool forward );
    void interrupt_reset_priv( );
    bool interrupt_cont_priv( );
    void interrupt_on_priv( );
    void interrupt_off_priv( );
};

static PyObject *InterruptError;

static signalhandler_type original_sigint_handler = 0;
static bool interrupt_has_occurred = false;
static bool interrupt_initialized = false;
static bool interrupt_on = false;
static bool interrupt_forward_sigint = false;

void interrupt_sigint_handler( int sig ) {

    interrupt_has_occurred = true;

    if ( interrupt_forward_sigint &&
	 original_sigint_handler != 0 &&
	 original_sigint_handler != SIG_IGN &&
	 original_sigint_handler != SIG_DFL &&
	 original_sigint_handler != SIG_ERR )
	(*original_sigint_handler)(sig);

    // reset the signal handler, for next time
    original_sigint_handler = signal(SIGINT,interrupt_sigint_handler);
}

void interrupt_init_priv( bool forward ) {
    if ( interrupt_initialized == false ) {
	interrupt_initialized = true;
	interrupt_on = true;
	original_sigint_handler = signal(SIGINT,interrupt_sigint_handler);
	interrupt_forward_sigint = forward;
    } else if ( interrupt_on ) {
	signalhandler_type handler = signal(SIGINT,interrupt_sigint_handler);
	if ( handler != interrupt_sigint_handler )
	    original_sigint_handler = handler;
    }
    interrupt_has_occurred = false;
}

static PyObject *interrupt_init_(PyObject *self, PyObject *args) {
    int forward = 1;

    PyArg_ParseTuple( args, "|i", &forward );
    interrupt_init_priv(forward != 0 ? true : false);
    Py_INCREF(Py_None);
    return Py_None;
}

void interrupt_reset_priv( ) {
    // reset() is a noop unless interrupt handling has been
    // initialized. This allows reset() & cont() to be
    // called whether SIGINT handling has been overriden or not.
    if ( interrupt_initialized == true ) {
	interrupt_init_priv( interrupt_forward_sigint );
    }
}

static PyObject *interrupt_reset_(PyObject *self, PyObject *args) {
    interrupt_reset_priv( );
    Py_INCREF(Py_None);
    return Py_None;
}

bool interrupt_cont_priv( ) {
    return interrupt_initialized ? (interrupt_has_occurred ? false : true) : true;
}

static PyObject *interrupt_cont_(PyObject *self, PyObject *args) {
    if ( interrupt_cont_priv( ) ) {
	Py_INCREF(Py_true);
	return Py_true;
    } else {
	Py_INCREF(Py_false);
	return Py_false;
    }
}

void interrupt_off_priv( ) {
    if ( interrupt_initialized == true && interrupt_on == true ) {
	signal(SIGINT,original_sigint_handler);
	interrupt_on = false;
	original_sigint_handler = 0;
	interrupt_has_occurred = false;
    }
}

static PyObject *interrupt_off_(PyObject *self, PyObject *args) {
    interrupt_off_priv( );
    Py_INCREF(Py_None);
    return Py_None;
}

void interrupt_on_priv( ) {
          
    if ( interrupt_initialized == true && interrupt_on == false ) {
        original_sigint_handler = signal(SIGINT,interrupt_sigint_handler);
	interrupt_on = true;
	interrupt_has_occurred = false;
    }
}

static PyObject *interrupt_on_(PyObject *self, PyObject *args) {
    interrupt_on_priv( );
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *interrupt_sleep_(PyObject *self, PyObject *args) {
    sleep(30);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef InterruptMethods[] = {
  { "init", interrupt_init_, METH_VARARGS,
    "initialize interrupt handling" },
  { "reset", interrupt_reset_, METH_NOARGS,
    "reset interrupt flag" },
  { "cont", interrupt_cont_, METH_NOARGS,
    "returns false when interrupt has occurred" },
  { "off", interrupt_off_, METH_NOARGS,
    "turn interrupt processing off" },
  { "on", interrupt_on_, METH_NOARGS,
    "turn interrupt processing on" },
  { "sleep", interrupt_sleep_, METH_NOARGS,
    "for debugging sleep" },
  { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initinterrupt( ) {
    PyObject *m;
    m = Py_InitModule("interrupt", InterruptMethods);
    InterruptError = PyErr_NewException("interrupt.error", NULL, NULL);
    Py_INCREF(InterruptError);
    PyModule_AddObject(m, "error", InterruptError);
}