// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

#include <inttypes.h>

#include "drgnpy.h"

PyObject *Symbol_wrap(struct drgn_symbol *sym, PyObject *name_obj)
{
	Symbol *ret = call_tp_alloc(Symbol);
	if (ret) {
		ret->sym = sym;
		ret->name_obj = name_obj;
		Py_XINCREF(name_obj);
	}
	return (PyObject *)ret;
}

PyObject *Symbol_list_wrap(struct drgn_symbol **symbols, size_t count,
			   PyObject *name_obj)
{
	_cleanup_pydecref_ PyObject *list = PyList_New(count);
	if (!list) {
		drgn_symbols_destroy(symbols, count);
		return NULL;
	}
	for (size_t i = 0; i < count; i++) {
		PyObject *pysym = Symbol_wrap(symbols[i], name_obj);
		if (!pysym) {
			/* Free symbols which aren't yet added to list. */
			drgn_symbols_destroy(symbols, count);
			/* Free list and all symbols already added. */
			return NULL;
		}
		symbols[i] = NULL;
		PyList_SET_ITEM(list, i, pysym);
	}
	free(symbols);
	return_ptr(list);
}

static PyObject *Symbol_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
{
	struct drgn_symbol *sym;
	static char *keywords[] = {"name", "address", "size", "binding", "kind", NULL};
	PyObject *name_obj;
	struct index_arg address = {}, size = {};
	struct enum_arg binding = {
		.type = SymbolBinding_class,
	};
	struct enum_arg kind = {
		.type = SymbolKind_class,
	};

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O&O&O&O&:Symbol", keywords,
					 &PyUnicode_Type, &name_obj,
					 index_converter, &address,
					 index_converter, &size,
					 enum_converter, &binding,
					 enum_converter, &kind))
		return NULL;

	const char *name = PyUnicode_AsUTF8(name_obj);
	if (!name)
		return NULL;

	struct drgn_error *err = drgn_symbol_create(
		name, address.uvalue,size.uvalue, binding.value, kind.value,
		DRGN_LIFETIME_EXTERNAL, &sym);
	if (err)
		return set_drgn_error(err);

	PyObject *ret = Symbol_wrap(sym, name_obj);
	if (!ret)
		drgn_symbol_destroy(sym);
	return ret;
}

static void Symbol_dealloc(Symbol *self)
{
	drgn_symbol_destroy(self->sym);
	Py_XDECREF(self->name_obj);
	Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *Symbol_richcompare(Symbol *self, PyObject *other, int op)
{
	if (!PyObject_TypeCheck(other, &Symbol_type) ||
	    (op != Py_EQ && op != Py_NE))
		Py_RETURN_NOTIMPLEMENTED;
	bool ret = drgn_symbol_eq(self->sym, ((Symbol *)other)->sym);
	if (op == Py_NE)
		ret = !ret;
	Py_RETURN_BOOL(ret);
}

static PyObject *Symbol_get_name(Symbol *self, void *arg)
{
	return PyUnicode_FromString(drgn_symbol_name(self->sym));
}

static PyObject *Symbol_get_address(Symbol *self, void *arg)
{
	return PyLong_FromUInt64(drgn_symbol_address(self->sym));
}

static PyObject *Symbol_get_size(Symbol *self, void *arg)
{
	return PyLong_FromUInt64(drgn_symbol_size(self->sym));
}

static PyObject *Symbol_get_binding(Symbol *self, void *arg)
{
	return PyObject_CallFunction(SymbolBinding_class, "k",
				     (unsigned long)drgn_symbol_binding(self->sym));
}

static PyObject *Symbol_get_kind(Symbol *self, void *arg)
{
	return PyObject_CallFunction(SymbolKind_class, "k",
				     (unsigned long)drgn_symbol_kind(self->sym));
}

static PyObject *Symbol_repr(Symbol *self)
{
	_cleanup_pydecref_ PyObject *name =
		PyUnicode_FromString(drgn_symbol_name(self->sym));
	if (!name)
		return NULL;
	_cleanup_pydecref_ PyObject *binding = Symbol_get_binding(self, NULL);
	if (!binding)
		return NULL;
	_cleanup_pydecref_ PyObject *kind = Symbol_get_kind(self, NULL);
	if (!kind)
		return NULL;

	char address[19], size[19];
	sprintf(address, "0x%" PRIx64, drgn_symbol_address(self->sym));
	sprintf(size, "0x%" PRIx64, drgn_symbol_size(self->sym));
	return PyUnicode_FromFormat("Symbol(name=%R, address=%s, size=%s, binding=%R, kind=%R)",
				    name, address, size, binding, kind);
}

static PyGetSetDef Symbol_getset[] = {
	{"name", (getter)Symbol_get_name, NULL, drgn_Symbol_name_DOC},
	{"address", (getter)Symbol_get_address, NULL, drgn_Symbol_address_DOC},
	{"size", (getter)Symbol_get_size, NULL, drgn_Symbol_size_DOC},
	{"binding", (getter)Symbol_get_binding, NULL, drgn_Symbol_binding_DOC},
	{"kind", (getter)Symbol_get_kind, NULL, drgn_Symbol_kind_DOC},
	{},
};

PyTypeObject Symbol_type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	.tp_name = "_drgn.Symbol",
	.tp_basicsize = sizeof(Symbol),
	.tp_dealloc = (destructor)Symbol_dealloc,
	.tp_repr = (reprfunc)Symbol_repr,
	// Only references str objects, no GC needed.
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_doc = drgn_Symbol_DOC,
	.tp_richcompare = (richcmpfunc)Symbol_richcompare,
	.tp_getset = Symbol_getset,
	.tp_new = Symbol_new,
};
