/*  hunspell.c
 *
 *  Copyright (C) 2009 - Sayamindu Dasgupta <sayamindu@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <Python.h>
#include <hunspell.h>


#ifndef PyVarObject_HEAD_INIT
    #define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
#endif

/* Compatibility python3 defines for python2 */
#if PY_MAJOR_VERSION < 3
    #define PyInt_FromLong PyLong_FromLong
    #define PyBytes_FromString  PyString_FromString
#endif /* PY_MAJOR_VERSION < 3 */


/****************************************
                HunSpell
****************************************/

static PyObject *HunSpellError;

typedef struct {
    PyObject_HEAD
    Hunhandle *handle;
    const char *encoding;
} HunSpell;

static int
HunSpell_init(HunSpell * self, PyObject *args, PyObject *kwds)
{
    PyObject *dpath = NULL; /* PyBytes in py3 PyString in py2 */
    PyObject *apath = NULL;
    FILE *fh;

#if PY_VERSION_HEX < 0x03010000
    /* TODO: Please review if there is any shorter/nicer;less clumsy way to convert args to  PyStrings using Py_FileSystemDefaultEncoding in python 2.x */
    const char * dpath_ptr = NULL;
    const char * apath_ptr = NULL;
    if (!PyArg_ParseTuple(args, "etet", Py_FileSystemDefaultEncoding, &dpath_ptr, Py_FileSystemDefaultEncoding, &apath_ptr))
        return 1;
    dpath = PyString_FromString(dpath_ptr);
    apath = PyString_FromString(apath_ptr);
#else
    if (!PyArg_ParseTuple(args, "O&O&", PyUnicode_FSConverter, &dpath, PyUnicode_FSConverter, &apath))
        return 1;
#endif
    /* Some versions of Hunspell_create() will succeed even if
    * there are no dictionary files. So test for permissions.
    */
    /* TODO: consider  _Py_fopen for py3.x here ? */
    fh = fopen(PyBytes_AsString(dpath), "r");
    if (fh) {
        fclose(fh);
    } else {
        PyErr_SetFromErrno(HunSpellError);
        /* TODO: Py_DECREF(*path); */
        return -1;
    }
    fh = fopen(PyBytes_AsString(apath), "r");
    if (fh) {
        fclose(fh);
    } else {
        PyErr_SetFromErrno(HunSpellError);
        return -1;
    }

    self->handle = Hunspell_create(PyBytes_AsString(apath), PyBytes_AsString(dpath));
    if(!self->handle) {
        PyErr_SetString(HunSpellError, "Cannot open dictionary");
        return -1;
    }
    self->encoding = Hunspell_get_dic_encoding(self->handle);
    Py_DECREF(dpath);
    Py_DECREF(apath);
    return 0;
}

static void
HunSpell_dealloc(HunSpell * self)
{
    Hunspell_destroy(self->handle);
    Py_TYPE(self)->tp_free((PyObject *)self);
}

static PyObject *
HunSpell_get_dic_encoding(HunSpell * self, PyObject *args)
{
    return Py_BuildValue("s", self->encoding);
}

static PyObject *
HunSpell_spell(HunSpell * self, PyObject *args)
{
    char *word;
    int retvalue;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
        return NULL;
    retvalue = Hunspell_spell(self->handle, word);
    PyMem_Free(word);

    return PyBool_FromLong(retvalue);
}


static PyObject *
HunSpell_suggest(HunSpell * self, PyObject *args)
{
    char *word, **slist;
    int i, num_slist, ret;
    PyObject *slist_list, *pystr;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
        return NULL;

    slist_list = PyList_New(0);
    if (!slist_list) {
        return NULL;
    }
    num_slist = Hunspell_suggest(self->handle, &slist, word);
    PyMem_Free(word);

    for (i = 0, ret = 0; !ret && i < num_slist; i++) {
        pystr = PyBytes_FromString(slist[i]);
        if (!pystr)
            break;
        ret = PyList_Append(slist_list, pystr);
        Py_DECREF(pystr);
    }

    Hunspell_free_list(self->handle, &slist, num_slist);
    return slist_list;
}

static PyObject *
HunSpell_analyze(HunSpell * self, PyObject *args)
{
    char *word, **slist;
    int i, num_slist, ret;
    PyObject *slist_list, *pystr;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
        return NULL;

    slist_list = PyList_New(0);
    if (!slist_list) {
        return NULL;
    }
    num_slist = Hunspell_analyze(self->handle, &slist, word);
    PyMem_Free(word);

    for (i = 0, ret = 0; !ret && i < num_slist; i++) {
        pystr = PyBytes_FromString(slist[i]);
        if (!pystr)
            break;
        ret = PyList_Append(slist_list, pystr);
        Py_DECREF(pystr);
    }

    Hunspell_free_list(self->handle, &slist, num_slist);
    return slist_list;
}

static PyObject *
HunSpell_stem(HunSpell * self, PyObject *args)
{
    char *word, **slist;
    int i, num_slist, ret;
    PyObject *slist_list, *pystr;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
    return NULL;

    slist_list = PyList_New(0);
    if (!slist_list) {
        return NULL;
    }
    num_slist = Hunspell_stem(self->handle, &slist, word);
    PyMem_Free(word);

    for (i = 0, ret = 0; !ret && i < num_slist; i++) {
        pystr = PyBytes_FromString(slist[i]);
        if (!pystr)
            break;
        ret = PyList_Append(slist_list, pystr);
        Py_DECREF(pystr);
    }

    Hunspell_free_list(self->handle, &slist, num_slist);
    return slist_list;
}

static PyObject *
HunSpell_generate(HunSpell * self, PyObject *args)
{
    char *word1, *word2, **slist;
    int i, num_slist, ret;
    PyObject *slist_list, *pystr;

    if (!PyArg_ParseTuple(args, "etet", self->encoding, &word1, self->encoding, &word2))
    return NULL;

    slist_list = PyList_New(0);
    if (!slist_list) {
        return NULL;
    }
    num_slist = Hunspell_generate(self->handle, &slist, word1, word2);
    PyMem_Free(word1);
    PyMem_Free(word2);

    for (i = 0, ret = 0; !ret && i < num_slist; i++) {
        pystr = PyBytes_FromString(slist[i]);
        if (!pystr)
            break;
        ret = PyList_Append(slist_list, pystr);
        Py_DECREF(pystr);
    }

    Hunspell_free_list(self->handle, &slist, num_slist);
    return slist_list;
}

static PyObject *
HunSpell_generate2(HunSpell * self, PyObject *args)
{
    char *word1, *desc, **slist;
    int i, num_slist, ret;
    PyObject *slist_list, *pystr;

    if (!PyArg_ParseTuple(args, "etet", self->encoding, &word1, self->encoding, &desc))
        return NULL;

    slist_list = PyList_New(0);
    if (!slist_list) {
        return NULL;
    }

    num_slist = Hunspell_generate2(self->handle, &slist, word1, &desc, 1);
    PyMem_Free(word1);
    PyMem_Free(desc);

    for (i = 0, ret = 0; !ret && i < num_slist; i++) {
        pystr = PyBytes_FromString(slist[i]);
        if (!pystr)
            break;
        ret = PyList_Append(slist_list, pystr);
        Py_DECREF(pystr);
    }

    Hunspell_free_list(self->handle, &slist, num_slist);
    return slist_list;
}

static PyObject *
HunSpell_add(HunSpell * self, PyObject *args)
{
    char *word;
    int retvalue;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
        return NULL;
    retvalue = Hunspell_add(self->handle, word);
    PyMem_Free(word);

    return PyLong_FromLong(retvalue);
}

static PyObject *
HunSpell_add_with_affix(HunSpell * self, PyObject *args)
{
    char *word, *example;
    int retvalue;

    if (!PyArg_ParseTuple(args, "etet", self->encoding, &word, self->encoding, &example))
        return NULL;
    retvalue = Hunspell_add_with_affix(self->handle, word, example);
    PyMem_Free(word);
    PyMem_Free(example);

    return PyLong_FromLong(retvalue);
}

static PyObject *
HunSpell_remove(HunSpell * self, PyObject *args)
{
    char *word;
    int retvalue;

    if (!PyArg_ParseTuple(args, "et", self->encoding, &word))
        return NULL;
    retvalue = Hunspell_remove(self->handle, word);
    PyMem_Free(word);

    return PyLong_FromLong(retvalue);
}

static PyMethodDef HunSpell_methods[] = {
    {"get_dic_encoding", (PyCFunction) HunSpell_get_dic_encoding,
     METH_NOARGS,
     "Gets encoding of loaded dictionary.\n\n"
     "Returns\n"
     "-------\n"
     "string : The encoding of currently used dic file (UTF-8, ISO8859-1, ...)"},

    {"spell", (PyCFunction) HunSpell_spell, METH_VARARGS,
     "Checks the spelling of the given word.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    Word to check.\n\n"
     "Returns\n"
     "-------\n"
     "bool : True if the word is correctly spelled else False"},

    {"suggest", (PyCFunction) HunSpell_suggest, METH_VARARGS,
     "Provide suggestions for the given word.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    Word for which we want suggestions\n\n"
     "Returns\n"
     "-------\n"
     "list of strings : The list of suggestions for input word. (No suggestion returns an empty list)."},

    {"analyze", (PyCFunction) HunSpell_analyze, METH_VARARGS,
     "Provide morphological analysis for the given word.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    Input word to analyze.\n\n"
     "Returns\n"
     "-------\n"
     "list of strings : Each string is a possible analysis of the input word. "
     "It contains the stem of the word (st:XXX) and some information about "
     "modifications done to get to the input word.\n"
     "For more informations see: man 4 hunspell (or https://sourceforge.net/projects/hunspell/files/Hunspell/Documentation/) "
     "in the \'Optional data fields\" section."},

    {"stem", (PyCFunction) HunSpell_stem, METH_VARARGS,
     "Stemmer method. It is a simplified version of analyze method.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to stem.\n\n"
     "Returns\n"
     "-------\n"
     "list of string : The possible stems of the input word."},

    {"generate", (PyCFunction) HunSpell_generate, METH_VARARGS,
     "Provide morphological generation for the given word using "
     "the second one as example.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to transform.\n"
     "word : string\n"
     "    The example to use as a generator\n\n"
     "Returns\n"
     "-------\n"
     "list of string : A list of possible transformations or "
     "an empty list if nothing were found"},

    {"generate2", (PyCFunction) HunSpell_generate2, METH_VARARGS,
     "Provide morphological generation for the given word "
     "the second one as example.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to transform.\n"
     "tags : string\n"
     "    String of an analyzed word\n\n"
     "Returns\n"
     "-------\n"
     "list of string : A list of possible transformations or "
     "an empty list if nothing were found"},

    {"add", (PyCFunction) HunSpell_add, METH_VARARGS,
     "Adds the given word into the runtime dictionary.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to add in the dictionnary\n\n"
     "Returns\n"
     "-------\n"
     "int : 0 if success, hunspell program error code else."},

    {"add_with_affix", (PyCFunction) HunSpell_add_with_affix, METH_VARARGS,
     "Adds the given word with affix flags of the example (a dictionary word) "
     "into the runtime dictionary.\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to transform.\n"
     "word : string\n"
     "    The example to use to find flags\n\n"
     "Returns\n"
     "-------\n"
     "int : 0 if success, hunspell program error code else."},

    {"remove", (PyCFunction) HunSpell_remove, METH_VARARGS,
     "Removes the given word from the runtime dictionary\n\n"
     "Parameters\n"
     "----------\n"
     "word : string\n"
     "    The word to remove from the dictionnary\n\n"
     "Returns\n"
     "-------\n"
     "int : 0 if success, hunspell program error code else."},

    {NULL}
};

static PyTypeObject HunSpellType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "HunSpell",        /* tp_name */
    sizeof(HunSpell),    /* tp_basicsize */
    0,            /* tp_itemsize */
    (destructor) HunSpell_dealloc,    /* tp_dealloc */
    0,            /* 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 */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,    /* tp_flags */
    "HunSpell binding. \n\n"
    "Instantiation goes like this:\n"
    ">>> hobj = HunSpell('/path/to/dict.dic', '/path/to/dict.aff')",    /* tp_doc */
    0,            /* tp_traverse */
    0,            /* tp_clear */
    0,            /* tp_richcompare */
    0,            /* tp_weaklistoffset */
    0,            /* tp_iter */
    0,            /* tp_iternext */
    HunSpell_methods,    /* tp_methods */
    0,            /* tp_members */
    0,            /* tp_getset */
    0,            /* tp_base */
    0,            /* tp_dict */
    0,            /* tp_descr_get */
    0,            /* tp_descr_set */
    0,            /* tp_dictoffset */
    (initproc) HunSpell_init,    /* tp_init */
    0,            /* tp_alloc */
    0,            /* tp_new */
};

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef hunspellmodule = {
    PyModuleDef_HEAD_INIT,
    "hunspell",    /* name of module */
    NULL,        /* module documentation, may be NULL */
    -1, /* TODO */    /* size of per-interpreter state of the module,
               or -1 if the module keeps state in global variables. */
    HunSpell_methods
};
#endif

/******************** Module Initialization function ****************/

#if PY_MAJOR_VERSION >= 3
PyObject*
PyInit_hunspell(void)
{
    PyObject *mod;

    /* Create the module */
    mod = PyModule_Create(&hunspellmodule);
    if (mod == NULL) {
        return NULL;
    }

    /* Fill in some slots in the type, and make it ready */
    HunSpellType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&HunSpellType) < 0) {
        return NULL;
    }
    /* Add the type to the module. */
    Py_INCREF(&HunSpellType);
    PyModule_AddObject(mod, "HunSpell", (PyObject *)&HunSpellType);
    HunSpellError = PyErr_NewException("hunspell.HunSpellError", NULL, NULL);
    Py_INCREF(HunSpellError);
    PyModule_AddObject(mod, "HunSpellError", HunSpellError);
    return mod;
}
#else
PyObject*
inithunspell(void)
{
    PyObject *mod;

    /* Create the module */
    mod = Py_InitModule3("hunspell", NULL,
                         "An extension for the Hunspell spell checker engine");
    if (mod == NULL) {
        return NULL;
    }

    /* Fill in some slots in the type, and make it ready */
    HunSpellType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&HunSpellType) < 0) {
        return NULL;
    }
    /* Add the type to the module. */
    Py_INCREF(&HunSpellType);
    PyModule_AddObject(mod, "HunSpell", (PyObject *)&HunSpellType);
    HunSpellError = PyErr_NewException("hunspell.HunSpellError", NULL, NULL);
    Py_INCREF(HunSpellError);
    PyModule_AddObject(mod, "HunSpellError", HunSpellError);
    return mod;
}
#endif
