#include "parts.h"
#include "util.h"

// Test PyModule_* API

/* unittest Cases that use these functions are in:
 * Lib/test/test_capi/test_module.py
 */

static PyObject *
module_from_slots_empty(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_null(PyObject *self, PyObject *spec)
{
    return PyModule_FromSlotsAndSpec(NULL, spec);
}

static PyObject *
module_from_slots_name(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_name, "currently ignored..."},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_doc(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_doc, "the docstring"},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_size(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_state_size, (void*)123},
        {0},
    };
    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
    if (!mod) {
        return NULL;
    }
    return mod;
}

static PyObject *
a_method(PyObject *self, PyObject *arg)
{
    return PyTuple_Pack(2, self, arg);
}

static PyMethodDef a_methoddef_array[] = {
    {"a_method", a_method, METH_O},
    {0},
};

static PyObject *
module_from_slots_methods(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_methods, a_methoddef_array},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static int noop_traverse(PyObject *self, visitproc visit, void *arg) {
    return 0;
}
static int noop_clear(PyObject *self) { return 0; }
static void noop_free(void *self) { }

static PyObject *
module_from_slots_gc(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_state_traverse, noop_traverse},
        {Py_mod_state_clear, noop_clear},
        {Py_mod_state_free, noop_free},
        {0},
    };
    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
    if (!mod) {
        return NULL;
    }
    if (PyModule_Add(mod, "traverse", PyLong_FromVoidPtr(&noop_traverse)) < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    if (PyModule_Add(mod, "clear", PyLong_FromVoidPtr(&noop_clear)) < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    if (PyModule_Add(mod, "free", PyLong_FromVoidPtr(&noop_free)) < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    return mod;
}

static const char test_token;

static PyObject *
module_from_slots_token(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_token, (void*)&test_token},
        {0},
    };
    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
    if (!mod) {
        return NULL;
    }
    void *got_token;
    if (PyModule_GetToken(mod, &got_token) < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    assert(got_token == &test_token);
    return mod;
}

static int
simple_exec(PyObject *module)
{
    return PyModule_AddIntConstant(module, "a_number", 456);
}

static PyObject *
module_from_slots_exec(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_exec, simple_exec},
        {0},
    };
    PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
    if (!mod) {
        return NULL;
    }
    int res = PyObject_HasAttrStringWithError(mod, "a_number");
    if (res < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    assert(res == 0);
    if (PyModule_Exec(mod) < 0) {
        Py_DECREF(mod);
        return NULL;
    }
    return mod;
}

static PyObject *
create_attr_from_spec(PyObject *spec, PyObject *def)
{
    assert(!def);
    return PyObject_GetAttrString(spec, "_gimme_this");
}

static PyObject *
module_from_slots_create(PyObject *self, PyObject *spec)
{
    PyModuleDef_Slot slots[] = {
        {Py_mod_create, create_attr_from_spec},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}


static int
slot_from_object(PyObject *obj)
{
    PyObject *slot_id_obj = PyObject_GetAttrString(obj, "_test_slot_id");
    if (slot_id_obj == NULL) {
        return -1;
    }
    int slot_id = PyLong_AsInt(slot_id_obj);
    if (PyErr_Occurred()) {
        return -1;
    }
    return slot_id;
}

static PyObject *
module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
{
    int slot_id = slot_from_object(spec);
    if (slot_id < 0) {
        return NULL;
    }
    PyModuleDef_Slot slots[] = {
        {slot_id, "anything"},
        {slot_id, "anything else"},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_slots_null_slot(PyObject *self, PyObject *spec)
{
    int slot_id = slot_from_object(spec);
    if (slot_id < 0) {
        return NULL;
    }
    PyModuleDef_Slot slots[] = {
        {slot_id, NULL},
        {0},
    };
    return PyModule_FromSlotsAndSpec(slots, spec);
}

static PyObject *
module_from_def_slot(PyObject *self, PyObject *spec)
{
    int slot_id = slot_from_object(spec);
    if (slot_id < 0) {
        return NULL;
    }
    PyModuleDef_Slot slots[] = {
        {slot_id, "anything"},
        {0},
    };
    PyModuleDef def = {
        PyModuleDef_HEAD_INIT,
        .m_name = "currently ignored",
        .m_slots = slots,
    };
    // PyModuleDef is normally static; the real requirement is that it
    // must outlive its module.
    // Here, module creation fails, so it's fine on the stack.
    PyObject *result = PyModule_FromDefAndSpec(&def, spec);
    assert(result == NULL);
    return result;
}

static int
another_exec(PyObject *module)
{
    /* Make sure simple_exec was called */
    assert(PyObject_HasAttrString(module, "a_number"));

    /* Add or negate a global called 'another_number'  */
    PyObject *another_number;
    if (PyObject_GetOptionalAttrString(module, "another_number",
                                       &another_number) < 0) {
        return -1;
    }
    if (!another_number) {
        return PyModule_AddIntConstant(module, "another_number", 789);
    }
    PyObject *neg_number = PyNumber_Negative(another_number);
    Py_DECREF(another_number);
    if (!neg_number) {
        return -1;
    }
    int result = PyObject_SetAttrString(module, "another_number",
                                        neg_number);
    Py_DECREF(neg_number);
    return result;
}

static PyObject *
module_from_def_multiple_exec(PyObject *self, PyObject *spec)
{
    static PyModuleDef_Slot slots[] = {
        {Py_mod_exec, simple_exec},
        {Py_mod_exec, another_exec},
        {Py_mod_gil, Py_MOD_GIL_NOT_USED},
        {0},
    };
    static PyModuleDef def = {
        PyModuleDef_HEAD_INIT,
        .m_name = "currently ignored",
        .m_slots = slots,
    };
    return PyModule_FromDefAndSpec(&def, spec);
}

static PyObject *
pymodule_exec(PyObject *self, PyObject *module)
{
    if (PyModule_Exec(module) < 0) {
        return NULL;
    }
    Py_RETURN_NONE;
}

static PyObject *
pymodule_get_token(PyObject *self, PyObject *module)
{
    void *token;
    if (PyModule_GetToken(module, &token) < 0) {
        return NULL;
    }
    return PyLong_FromVoidPtr(token);
}

static PyObject *
pymodule_get_def(PyObject *self, PyObject *module)
{
    PyModuleDef *def = PyModule_GetDef(module);
    if (!def && PyErr_Occurred()) {
        return NULL;
    }
    return PyLong_FromVoidPtr(def);
}

static PyObject *
pymodule_get_state_size(PyObject *self, PyObject *module)
{
    Py_ssize_t size;
    if (PyModule_GetStateSize(module, &size) < 0) {
        return NULL;
    }
    return PyLong_FromSsize_t(size);
}

static PyMethodDef test_methods[] = {
    {"module_from_slots_empty", module_from_slots_empty, METH_O},
    {"module_from_slots_null", module_from_slots_null, METH_O},
    {"module_from_slots_name", module_from_slots_name, METH_O},
    {"module_from_slots_doc", module_from_slots_doc, METH_O},
    {"module_from_slots_size", module_from_slots_size, METH_O},
    {"module_from_slots_methods", module_from_slots_methods, METH_O},
    {"module_from_slots_gc", module_from_slots_gc, METH_O},
    {"module_from_slots_token", module_from_slots_token, METH_O},
    {"module_from_slots_exec", module_from_slots_exec, METH_O},
    {"module_from_slots_create", module_from_slots_create, METH_O},
    {"module_from_slots_repeat_slot", module_from_slots_repeat_slot, METH_O},
    {"module_from_slots_null_slot", module_from_slots_null_slot, METH_O},
    {"module_from_def_multiple_exec", module_from_def_multiple_exec, METH_O},
    {"module_from_def_slot", module_from_def_slot, METH_O},
    {"pymodule_get_token", pymodule_get_token, METH_O},
    {"pymodule_get_def", pymodule_get_def, METH_O},
    {"pymodule_get_state_size", pymodule_get_state_size, METH_O},
    {"pymodule_exec", pymodule_exec, METH_O},
    {NULL},
};

int
_PyTestCapi_Init_Module(PyObject *m)
{
#define ADD_INT_MACRO(C) if (PyModule_AddIntConstant(m, #C, C) < 0) return -1;
    ADD_INT_MACRO(Py_mod_create);
    ADD_INT_MACRO(Py_mod_exec);
    ADD_INT_MACRO(Py_mod_multiple_interpreters);
    ADD_INT_MACRO(Py_mod_gil);
    ADD_INT_MACRO(Py_mod_name);
    ADD_INT_MACRO(Py_mod_doc);
    ADD_INT_MACRO(Py_mod_state_size);
    ADD_INT_MACRO(Py_mod_methods);
    ADD_INT_MACRO(Py_mod_state_traverse);
    ADD_INT_MACRO(Py_mod_state_clear);
    ADD_INT_MACRO(Py_mod_state_free);
    ADD_INT_MACRO(Py_mod_token);
#undef ADD_INT_MACRO
    if (PyModule_Add(m, "module_test_token",
                     PyLong_FromVoidPtr((void*)&test_token)) < 0)
    {
        return -1;
    }
    return PyModule_AddFunctions(m, test_methods);
}
