
#include <switch.h>
#include "freeswitch_python.h"
using namespace PYTHON;

#define py_init_vars() cb_function = cb_arg = hangup_func = hangup_func_arg = NULL; hh = mark = 0; TS = NULL

#define PyString_AsString PyUnicode_AsUTF8
#define PyString_Check PyUnicode_Check

Session::Session():CoreSession()
{
	py_init_vars();
}

Session::Session(char *nuuid, CoreSession *a_leg):CoreSession(nuuid, a_leg)
{
	py_init_vars();
}

Session::Session(switch_core_session_t *new_session):CoreSession(new_session)
{
	py_init_vars();
}
static switch_status_t python_hanguphook(switch_core_session_t *session_hungup);

void Session::destroy(void)
{

	if (!allocated) {
		return;
	}

	if (session) {
		if (!channel) {
			channel = switch_core_session_get_channel(session);
		}
		switch_channel_set_private(channel, "CoreSession", NULL);
		switch_core_event_hook_remove_state_change(session, python_hanguphook);
	}

	if (hangup_func) {
		Py_DECREF(hangup_func);
        hangup_func = NULL;
    }

	if (hangup_func_arg) {
		Py_DECREF(hangup_func_arg);
		hangup_func_arg = NULL;
	}

	if (cb_function) {
		Py_DECREF(cb_function);
		cb_function = NULL;
	}

	if (cb_arg) {
		Py_DECREF(cb_arg);
		cb_arg = NULL;
	}

	CoreSession::destroy();
}

Session::~Session()
{
	destroy();
}

bool Session::begin_allow_threads()
{

	do_hangup_hook();

	if (!TS) {
		TS = PyEval_SaveThread();
		if (channel) {
			switch_channel_set_private(channel, "SwapInThreadState", TS);
		}
		return true;
	}

	return false;
}

bool Session::end_allow_threads()
{

	if (!TS) {
		return false;
	}

	PyEval_RestoreThread(TS);
	TS = NULL;

	if (channel) {
		switch_channel_set_private(channel, "SwapInThreadState", NULL);
	}

	do_hangup_hook();

	return true;
}

void Session::setPython(PyObject *state)
{
	Py = state;
}

void Session::setSelf(PyObject *state)
{
	Self = state;
}

PyObject *Session::getPython()
{
	return Py;
}


bool Session::ready()
{
	bool r;

	sanity_check(false);
	r = switch_channel_ready(channel) != 0;

	/*! this is called every time ready is called as a workaround to
	  make it threadsafe.  it sets a flag, and all the places where it
	  comes in and out of threadswap, check it.  so the end result is
	  you still get the hangup hook executed pretty soon after you
	  hangup.  */
	do_hangup_hook();

	return r;
}

void Session::check_hangup_hook()
{
	if (hangup_func && (hook_state == CS_HANGUP || hook_state == CS_ROUTING)) {
		hh++;
	}
}

void Session::do_hangup_hook()
{
	PyObject *arglist;
	const char *what = hook_state == CS_HANGUP ? "hangup" : "transfer";

	if (hh && !mark) {
		mark++;

		if (hangup_func) {

			if (!PyCallable_Check(hangup_func)) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "function not callable\n");
				return;
			}

			if (!Self) {
				mod_python_conjure_session(NULL, session);
			}

			if (hangup_func_arg) {
				arglist = Py_BuildValue("(OsO)", Self, what, hangup_func_arg);
			} else {
				arglist = Py_BuildValue("(Os)", Self, what);
			}

#if PY_VERSION_HEX <= 0x03080000
			if (!PyEval_CallObject(hangup_func, arglist)) {
				PyErr_Print();
			}
#else 
			if (!PyObject_CallObject(hangup_func, arglist)) {
				PyErr_Print();
			}
#endif

			Py_XDECREF(arglist);
			Py_XDECREF(hangup_func_arg);
		}
	}

}

static switch_status_t python_hanguphook(switch_core_session_t *session_hungup)
{
	switch_channel_t *channel = switch_core_session_get_channel(session_hungup);
	CoreSession *coresession = NULL;
	switch_channel_state_t state = switch_channel_get_state(channel);

	if ((coresession = (CoreSession *) switch_channel_get_private(channel, "CoreSession"))) {
		if (coresession->hook_state != state) {
			coresession->hook_state = state;
			coresession->check_hangup_hook();
		}
	}

	return SWITCH_STATUS_SUCCESS;
}


void Session::setHangupHook(PyObject *pyfunc, PyObject *arg)
{

	if (!PyCallable_Check(pyfunc)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Hangup hook is not a python function.\n");
		return;
	}

	if (hangup_func) {
		if (session) {
			switch_core_event_hook_remove_state_change(session, python_hanguphook);
		}
		Py_XDECREF(hangup_func);
		hangup_func = NULL;
	}

	if (hangup_func_arg) {
		Py_XDECREF(hangup_func_arg);
		hangup_func_arg = NULL;
	}

	hangup_func = pyfunc;
	hangup_func_arg = arg;

	Py_XINCREF(hangup_func);

	if (hangup_func_arg) {
		Py_XINCREF(hangup_func_arg);
	}

	switch_channel_set_private(channel, "CoreSession", this);
	hook_state = switch_channel_get_state(channel);
	switch_core_event_hook_add_state_change(session, python_hanguphook);

}

void Session::unsetInputCallback(void)
{
	if (cb_function) {
        Py_XDECREF(cb_function);
        cb_function = NULL;
    }

    if (cb_arg) {
        Py_XDECREF(cb_arg);
        cb_arg = NULL;
    }

	args.input_callback = NULL;
	ap = NULL;

}

void Session::setInputCallback(PyObject *cbfunc, PyObject *funcargs)
{

	if (!PyCallable_Check(cbfunc)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Hangup hook is not a python function.\n");
		return;
	}

	if (cb_function) {
		Py_XDECREF(cb_function);
		cb_function = NULL;
	}

	if (cb_arg) {
		Py_XDECREF(cb_arg);
		cb_arg = NULL;
	}

	cb_function = cbfunc;
	cb_arg = funcargs;
	args.buf = this;
    switch_channel_set_private(channel, "CoreSession", this);

	Py_XINCREF(cb_function);

	if (cb_arg) {
		Py_XINCREF(cb_arg);
	}

    args.input_callback = dtmf_callback;
    ap = &args;

}

switch_status_t Session::run_dtmf_callback(void *input, switch_input_type_t itype)
{

	PyObject *pyresult, *arglist, *io = NULL;
	int ts = 0;
	char *str = NULL, *what = (char*)"";

	if (TS) {
		ts++;
		end_allow_threads();
	}

	if (!PyCallable_Check(cb_function)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "function not callable\n");
		return SWITCH_STATUS_FALSE;
	}

	if (itype == SWITCH_INPUT_TYPE_DTMF) {
		switch_dtmf_t *dtmf = (switch_dtmf_t *) input;
		io = mod_python_conjure_DTMF(dtmf->digit, dtmf->duration);
		what = (char*)"dtmf";
	} else if (itype == SWITCH_INPUT_TYPE_EVENT){
		what = (char*)"event";
		io = mod_python_conjure_event((switch_event_t *) input);
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "unsupported type!\n");
		return SWITCH_STATUS_FALSE;
	}

	if (!Self) {
		mod_python_conjure_session(NULL, session);
	}

	if (cb_arg) {
		arglist = Py_BuildValue("(OsOO)", Self, what, io, cb_arg);
	} else {
		arglist = Py_BuildValue("(OsO)", Self, what, io);
	}
#if PY_VERSION_HEX <= 0x03080000
		pyresult = PyEval_CallObject(cb_function, arglist);
#else 
		pyresult = PyObject_CallObject(cb_function, arglist);
#endif
	if (pyresult) {
		str = (char *) PyString_AsString(pyresult);
	} else {
		PyErr_Print();
	}

	Py_XDECREF(arglist);
	Py_XDECREF(io);

	if (ts) {
		begin_allow_threads();
	}

	if (str) {
		return process_callback_result(str);
	}

	return SWITCH_STATUS_FALSE;
}
