/*
 * Copyright (c) 2016-2021 Satlab A/S <satlab@satlab.com>
 * All rights reserved. Do not distribute without permission.
 */

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>

#include <satlab/srs3.h>

#define PROP_CLIENT_SUBSYS_SPEC_CAPSULE	"prop_client.subsys_spec"

/* SRS3 object */
typedef struct {
	PyObject_HEAD
	uint8_t node;
	uint32_t timeout;
	PyObject *prop;
	PyObject *csp;
} SRS3Object;

PyDoc_STRVAR(srs3_gwdt_reset_doc,
	     "Reset ground watchdog timer.\n\n"
	     "Parameters:\n"
	     "timeout (int) (optional): Timeout of the command in milliseconds (default 1000).\n\n"
	     "Returns:\n"
	     "None on success, raises exception on failure.\n\n");

static PyObject *SRS3_gwdt_reset(SRS3Object *self, PyObject *args, PyObject *kwds)
{
	int ret;

	static const char *kwlist[] = {"timeout", NULL};
	uint32_t timeout = self->timeout;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|I", (char **)kwlist, &timeout))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	ret = sl_srs3_gwdt_reset(self->node, timeout, SL_SRS3_GWDT_RESET_KEY);
	Py_END_ALLOW_THREADS
	if (ret < 0) {
		PyErr_Format(PyExc_TimeoutError, "Failed to reset GWDT: %d", ret);
		return NULL;
	}

	Py_RETURN_NONE;
}

PyDoc_STRVAR(srs3_gwdt_counter_doc,
	     "Get ground watchdog timer value.\n\n"
	     "Parameters:\n"
	     "timeout (int) (optional): Timeout of the command in milliseconds (default 1000).\n\n"
	     "Returns:\n"
	     "(int) the number of seconds until the gwdt resets on success, raises exception on failure.\n\n");

static PyObject *SRS3_gwdt_counter(SRS3Object *self, PyObject *args, PyObject *kwds)
{
	int ret;
	uint32_t counter;

	static const char *kwlist[] = {"timeout", NULL};
	uint32_t timeout = self->timeout;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|I", (char **)kwlist, &timeout))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	ret = sl_srs3_gwdt_counter(self->node, timeout, &counter);
	Py_END_ALLOW_THREADS
	if (ret < 0) {
		PyErr_Format(PyExc_TimeoutError, "Failed to read GWDT: %d", ret);
		return NULL;
	}

	return PyLong_FromLong((long)counter);
}

PyDoc_STRVAR(srs3_shell_run_doc,
	     "Run shell command.\n\n"
	     "Warning:\n"
	     "Be very careful with this command. This command should not be used during\n"
	     "nominal operations and is reserved solely for debug purposes.\n\n"
	     "Parameters:\n"
	     "cmd (string): Command to execute.\n"
	     "timeout (int) (optional): Timeout of the command in milliseconds (default 1000).\n\n"
	     "Returns:\n"
	     "None on success, raises exception on failure.\n\n");

static PyObject *SRS3_shell_run(SRS3Object *self, PyObject *args, PyObject *kwds)
{
	int ret;

	static const char *kwlist[] = {"cmd", "timeout", NULL};
	const char *cmd;
	uint32_t timeout = self->timeout;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|I", (char **)kwlist, &cmd, &timeout))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	ret = sl_srs3_shell_run(self->node, timeout, cmd);
	Py_END_ALLOW_THREADS
	if (ret < 0) {
		PyErr_Format(PyExc_TimeoutError, "Failed to run command: %d", ret);
		return NULL;
	}

	Py_RETURN_NONE;
}

static int SRS3_init(SRS3Object *self, PyObject *args, PyObject *kwds)
{
	int ret = -1;
	self->node = SL_SRS3_DEFAULT_ADDRESS;
	self->timeout = 1000;

	static const char *kwlist[] = {"node", "timeout", NULL};
	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|bI", (char **)kwlist, &self->node, &self->timeout))
		goto err;

	/* Get reference to prop client module */
	PyObject *pyprop = PyImport_ImportModule("satlab.prop_client");
	if (!pyprop)
		goto err;

	/* Get reference to PropNode class */
	PyObject *PropNode = PyObject_GetAttrString(pyprop, "PropNode");
	if (!PropNode)
		goto err_prop_node;

	/* Create capsule object with props */
	PyObject *props_capsule = PyCapsule_New((void *)&srs3_props, PROP_CLIENT_SUBSYS_SPEC_CAPSULE, NULL);
	if (!props_capsule)
		goto err_prop_capsule;

	/* Create PropNode object by calling constructor */
	self->prop = PyObject_CallFunction(PropNode, "bbOI", self->node, SL_PROP_DEFAULT_PORT, props_capsule, self->timeout);
	if (!self->prop)
		goto err_prop_ctor;

	/* Get reference to CSP module */
	PyObject *pycsp = PyImport_ImportModule("csp");
	if (!pycsp)
		goto err_csp;

	/* Get reference to CSPNode class */
	PyObject *CSPNode = PyObject_GetAttrString(pycsp, "CSPNode");
	if (!CSPNode)
		goto err_csp_node;

	/* Create CSPNode object by calling constructor */
	self->csp = PyObject_CallFunction(CSPNode, "b", self->node);
	if (!self->csp)
		goto err_csp_ctor;

	ret = 0;

err_csp_ctor:
	Py_DECREF(CSPNode);
err_csp_node:
	Py_DECREF(pycsp);
err_csp:
err_prop_ctor:
	Py_DECREF(props_capsule);
err_prop_capsule:
	Py_DECREF(PropNode);
err_prop_node:
	Py_DECREF(pyprop);
err:
	return ret;
}

static PyMemberDef SRS3_members[] = {
	{"prop", T_OBJECT_EX, offsetof(SRS3Object, prop), 0, "PropNode"},
	{"csp",  T_OBJECT_EX, offsetof(SRS3Object, csp),  0, "CSPNode"},
	{NULL}  /* Sentinel */
};

static PyMethodDef SRS3_methods[] = {
	{"gwdt_reset",   (PyCFunction)(void(*)(void))SRS3_gwdt_reset,   METH_VARARGS | METH_KEYWORDS, srs3_gwdt_reset_doc},
	{"gwdt_counter", (PyCFunction)(void(*)(void))SRS3_gwdt_counter, METH_VARARGS | METH_KEYWORDS, srs3_gwdt_counter_doc},
	{"shell_run",    (PyCFunction)(void(*)(void))SRS3_shell_run,    METH_VARARGS | METH_KEYWORDS, srs3_shell_run_doc},
	{NULL}  /* Sentinel */
};

static PyTypeObject SRS3Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	.tp_name = "srs3_client.SRS3",
	.tp_doc = "SRS3 object",
	.tp_basicsize = sizeof(SRS3Object),
	.tp_itemsize = 0,
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_new = PyType_GenericNew,
	.tp_init = (initproc) SRS3_init,
	.tp_members = SRS3_members,
	.tp_methods = SRS3_methods,
};

static struct PyModuleDef srs3_client_module = {
	.m_base = PyModuleDef_HEAD_INIT,
	.m_name = "srs3_client",
	.m_size = -1,
};

PyMODINIT_FUNC PyInit_srs3_client_bindings(void)
{
	if (PyType_Ready(&SRS3Type) < 0)
		return NULL;

	PyObject *m = PyModule_Create(&srs3_client_module);
	if (!m)
		return NULL;

	/* Add SRS3 object */
	Py_INCREF(&SRS3Type);
	if (PyModule_AddObject(m, "SRS3", (PyObject *) &SRS3Type) < 0) {
		Py_DECREF(&SRS3Type);
		Py_DECREF(m);
		return NULL;
	}

	PyModule_AddIntConstant(m, "DEFAULT_ADDRESS", SL_SRS3_DEFAULT_ADDRESS);

	return m;
}
