/*
 * hunspell.c
 * Python wrapper for the hunspell library.
 * Copyright (C) 2013 Kovid Goyal <kovid at kovidgoyal.net>
 *
 * Distributed under terms of the GPL3 license.
 */

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <new>
#include <string>
#include <hunspell.hxx>

typedef struct {
	PyObject_HEAD
    Hunspell *handle;
    char *encoding;
} Dictionary;

static PyObject *HunspellError = NULL;

static int
init_type(Dictionary *self, PyObject *args, PyObject *kwds) {
	char *dic = NULL, *aff = NULL;

    self->handle = NULL;
    self->encoding = NULL;

	if (!PyArg_ParseTuple(args, "ss", &dic, &aff)) return 1;

    try {
        self->handle = new (std::nothrow) Hunspell(aff, dic);
    } catch (const std::exception &ex) {
        PyErr_SetString(HunspellError, ex.what());
        return 1;
    } catch (const std::string &ex) {
        PyErr_SetString(HunspellError, ex.c_str());
        return 1;
    } catch (...) {
        PyErr_SetString(HunspellError, "Failed to create dictionary, unknown error");
        return 1;
    }
    if (self->handle == NULL) { PyErr_NoMemory(); return 1; }
    self->encoding = self->handle->get_dic_encoding();
    if (self->encoding == NULL) { delete self->handle; self->handle = NULL; PyErr_SetString(HunspellError, "Failed to get dictionary encoding"); return 1; }
	return 0;
}

static void
dealloc(Dictionary *self) {
    if (self->handle != NULL) delete self->handle;
    /* We do not free encoding, since it is managed by hunspell */
    self->encoding = NULL; self->handle = NULL;
	Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *
recognized(Dictionary *self, PyObject *args) {
	char *w = NULL;
	if (!PyArg_ParseTuple(args, "es", self->encoding, &w)) return NULL;
    std::string word(w);
    PyMem_Free(w);

    if (!self->handle->spell(word)) { Py_RETURN_FALSE;}
    Py_RETURN_TRUE;
}

static PyObject *
suggest(Dictionary *self, PyObject *args) {
	char *w = NULL;
	PyObject *ans, *temp;

	if (!PyArg_ParseTuple(args, "es", self->encoding, &w)) return NULL;
    const std::string word(w);
    PyMem_Free(w);

    const std::vector<std::string>& word_list = self->handle->suggest(word);
	ans = PyTuple_New(word_list.size());
    if (ans == NULL) return PyErr_NoMemory();
    Py_ssize_t i = 0;
    for(auto const& s: word_list) {
        temp = PyUnicode_Decode(s.c_str(), s.size(), self->encoding, "strict");
        if (temp == NULL) { Py_DECREF(ans); ans = NULL; break; }
        PyTuple_SET_ITEM(ans, i++, temp);
    }
	return ans;
}

static PyObject *
add(Dictionary *self, PyObject *args) {
	char *word = NULL;

	if (!PyArg_ParseTuple(args, "es", self->encoding, &word)) return NULL;
	if (self->handle->add(word) == 0) { PyMem_Free(word); Py_RETURN_TRUE; }
    PyMem_Free(word);
    Py_RETURN_FALSE;
}

static PyObject *
remove_word(Dictionary *self, PyObject *args) {
	char *word = NULL;

	if (!PyArg_ParseTuple(args, "es", self->encoding, &word)) return NULL;
	if (self->handle->remove(word) == 0) { PyMem_Free(word); Py_RETURN_TRUE; }
    PyMem_Free(word);
    Py_RETURN_FALSE;
}

static PyMethodDef HunSpell_methods[] = {
	{"recognized", (PyCFunction)recognized, METH_VARARGS,
	 "Checks the spelling of the given word. The word must be a unicode "
	 "object. If encoding of the word to the encoding of the dictionary fails, "
	 "a UnicodeEncodeError is raised. Returns False if the input word is not "
	 "recognized."},
	{"suggest", (PyCFunction)suggest, METH_VARARGS,
	 "Provide suggestions for the given word. The input word must be a unicode "
	 "object. If encoding of the word to the encoding of the dictionary fails, "
	 "a UnicodeEncodeError is raised. Returns the list of suggested words as "
	 "unicode objects."},
	{"add", (PyCFunction)add, METH_VARARGS,
	 "Adds the given word into the runtime dictionary"},
	{"remove", (PyCFunction)remove_word, METH_VARARGS,
	 "Removes the given word from the runtime dictionary"},
	{NULL}
};

static PyTypeObject DictionaryType = {
	PyVarObject_HEAD_INIT(NULL, 0)
    /* tp_name           */ "Dictionary",
    /* tp_basicsize      */ sizeof(Dictionary),
    /* tp_itemsize       */ 0,
    /* tp_dealloc        */ (destructor) dealloc,
    /* tp_print          */ 0,
    /* tp_getattr        */ 0,
    /* tp_setattr        */ 0,
    /* tp_compare        */ 0,
    /* tp_repr           */ 0,
    /* tp_as_number      */ 0,
    /* tp_as_sequence    */ 0,
    /* tp_as_mapping     */ 0,
    /* tp_hash           */ 0,
    /* tp_call           */ 0,
    /* tp_str            */ 0,
    /* tp_getattro       */ 0,
    /* tp_setattro       */ 0,
    /* tp_as_buffer      */ 0,
    /* tp_flags          */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    /* tp_doc            */ "Dictionary object",
    /* tp_traverse       */ 0,
    /* tp_clear          */ 0,
    /* tp_richcompare    */ 0,
    /* tp_weaklistoffset */ 0,
    /* tp_iter           */ 0,
    /* tp_iternext       */ 0,
    /* tp_methods        */ HunSpell_methods,
    /* tp_members        */ 0,
    /* tp_getset         */ 0,
    /* tp_base           */ 0,
    /* tp_dict           */ 0,
    /* tp_descr_get      */ 0,
    /* tp_descr_set      */ 0,
    /* tp_dictoffset     */ 0,
    /* tp_init           */ (initproc) init_type,
    /* tp_alloc          */ 0,
    /* tp_new            */ 0,
};

static int
exec_module(PyObject *mod) {
    HunspellError = PyErr_NewException((char*)"hunspell.HunspellError", NULL, NULL);
    if (HunspellError == NULL) return -1;
    PyModule_AddObject(mod, "HunspellError", HunspellError);

    // Fill in some slots in the type, and make it ready
    DictionaryType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&DictionaryType) < 0) return -1;
    // Add the type to the module.
    Py_INCREF(&DictionaryType);
    if (PyModule_AddObject(mod, "Dictionary", (PyObject *)&DictionaryType) != 0) return -1;

    return 0;
}

static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} };

static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT};

CALIBRE_MODINIT_FUNC PyInit_hunspell(void) {
	module_def.m_name = "hunspell";
	module_def.m_slots = slots;
	return PyModuleDef_Init(&module_def);
}
