// SPDX-License-Identifier: LGPL-2.1-or-later
// SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>

#include <gpiod.h>
#include <Python.h>

struct module_const {
	const char *name;
	long val;
};

static const struct module_const module_constants[] = {
	{
		.name = "VALUE_INACTIVE",
		.val = GPIOD_LINE_VALUE_INACTIVE,
	},
	{
		.name = "VALUE_ACTIVE",
		.val = GPIOD_LINE_VALUE_ACTIVE,
	},
	{
		.name = "DIRECTION_AS_IS",
		.val = GPIOD_LINE_DIRECTION_AS_IS,
	},
	{
		.name = "DIRECTION_INPUT",
		.val = GPIOD_LINE_DIRECTION_INPUT,
	},
	{
		.name = "DIRECTION_OUTPUT",
		.val = GPIOD_LINE_DIRECTION_OUTPUT,
	},
	{
		.name = "BIAS_AS_IS",
		.val = GPIOD_LINE_BIAS_AS_IS,
	},
	{
		.name = "BIAS_UNKNOWN",
		.val = GPIOD_LINE_BIAS_UNKNOWN,
	},
	{
		.name = "BIAS_DISABLED",
		.val = GPIOD_LINE_BIAS_DISABLED,
	},
	{
		.name = "BIAS_PULL_UP",
		.val = GPIOD_LINE_BIAS_PULL_UP,
	},
	{
		.name = "BIAS_PULL_DOWN",
		.val = GPIOD_LINE_BIAS_PULL_DOWN,
	},
	{
		.name = "DRIVE_PUSH_PULL",
		.val = GPIOD_LINE_DRIVE_PUSH_PULL,
	},
	{
		.name = "DRIVE_OPEN_DRAIN",
		.val = GPIOD_LINE_DRIVE_OPEN_DRAIN,
	},
	{
		.name = "DRIVE_OPEN_SOURCE",
		.val = GPIOD_LINE_DRIVE_OPEN_SOURCE,
	},
	{
		.name = "EDGE_NONE",
		.val = GPIOD_LINE_EDGE_NONE,
	},
	{
		.name = "EDGE_FALLING",
		.val = GPIOD_LINE_EDGE_FALLING,
	},
	{
		.name = "EDGE_RISING",
		.val = GPIOD_LINE_EDGE_RISING,
	},
	{
		.name = "EDGE_BOTH",
		.val = GPIOD_LINE_EDGE_BOTH,
	},
	{
		.name = "CLOCK_MONOTONIC",
		.val = GPIOD_LINE_CLOCK_MONOTONIC,
	},
	{
		.name = "CLOCK_REALTIME",
		.val = GPIOD_LINE_CLOCK_REALTIME,
	},
	{
		.name = "CLOCK_HTE",
		.val = GPIOD_LINE_CLOCK_HTE,
	},
	{
		.name = "EDGE_EVENT_TYPE_RISING",
		.val = GPIOD_EDGE_EVENT_RISING_EDGE,
	},
	{
		.name = "EDGE_EVENT_TYPE_FALLING",
		.val = GPIOD_EDGE_EVENT_FALLING_EDGE,
	},
	{
		.name = "INFO_EVENT_TYPE_LINE_REQUESTED",
		.val = GPIOD_INFO_EVENT_LINE_REQUESTED,
	},
	{
		.name = "INFO_EVENT_TYPE_LINE_RELEASED",
		.val = GPIOD_INFO_EVENT_LINE_RELEASED,
	},
	{
		.name = "INFO_EVENT_TYPE_LINE_CONFIG_CHANGED",
		.val = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
	},
	{ }
};

static PyObject *
module_is_gpiochip_device(PyObject *Py_UNUSED(self), PyObject *args)
{
	const char *path;
	int ret;

	ret =  PyArg_ParseTuple(args, "s", &path);
	if (!ret)
		return NULL;

	return PyBool_FromLong(gpiod_is_gpiochip_device(path));
}

static PyMethodDef module_methods[] = {
	{
		.ml_name = "is_gpiochip_device",
		.ml_meth = (PyCFunction)module_is_gpiochip_device,
		.ml_flags = METH_VARARGS,
	},
	{ }
};

extern PyTypeObject chip_type;
extern PyTypeObject line_config_type;
extern PyTypeObject line_settings_type;
extern PyTypeObject request_type;

static PyTypeObject *types[] = {
	&chip_type,
	&line_config_type,
	&line_settings_type,
	&request_type,
	NULL,
};

static int module_exec(PyObject* module)
{
	const struct module_const *modconst;
	PyObject *all;
	PyTypeObject **type;
	int ret;

	ret = PyModule_AddStringConstant(module, "api_version",
					 gpiod_api_version());

	if (ret < 0)
		return -1;

	all = PyList_New(0);
	if (!all)
		return -1;

	ret = PyModule_AddObjectRef(module, "__all__", all);
	Py_DECREF(all);
	if (ret)
		return -1;

	for (type = types; *type; type++) {
		ret = PyModule_AddType(module, *type);
		if (ret < 0)
			return -1;
	}

	for (modconst = module_constants; modconst->name; modconst++) {
		ret = PyModule_AddIntConstant(module, modconst->name,
					      modconst->val);
		if (ret < 0)
			return -1;
	}

	return 0;
}

static struct PyModuleDef_Slot module_slots[] = {
	{ Py_mod_exec, module_exec },
#if PY_VERSION_HEX >= 0x030E0000 && defined(Py_GIL_DISABLED)
	{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
	{0, NULL},
};

static PyModuleDef module_def = {
	PyModuleDef_HEAD_INIT,
	.m_name = "gpiod._ext",
	.m_methods = module_methods,
	.m_slots = module_slots,
};

PyMODINIT_FUNC PyInit__ext(void)
{
	return PyModuleDef_Init(&module_def);
}
