/*
 * sqlite_extension.cpp
 * Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
 *
 * Distributed under terms of the GPL3 license.
 */

#define PY_SSIZE_T_CLEAN
#define UNICODE
#include <Python.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <cstring>
#include <sqlite3ext.h>
#include <unicode/unistr.h>
#include <unicode/uchar.h>
#include <unicode/translit.h>
#include <unicode/errorcode.h>
#include <unicode/brkiter.h>
#include <unicode/uscript.h>
#if __has_include(<libstemmer.h>)
#include <libstemmer.h>
#else
#include <libstemmer/libstemmer.h>
#endif
#include "../utils/cpp_binding.h"
SQLITE_EXTENSION_INIT1

typedef int (*token_callback_func)(void *, int, const char *, int, int, int);


// Converting SQLITE text to ICU strings {{{
// UTF-8 decode taken from: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/

static const uint8_t utf8_data[] = {
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
    1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
    7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
    8,   8,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
    0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3,                                                 // e0..ef
    0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,                                                 // f0..ff
    0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1,                                                 // s0..s0
    1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
    1,   2,   1,   1,   1,   1,   1,   2,   1,   2,   1,   1,   1,   1,   1,   1,   1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
    1,   2,   1,   1,   1,   1,   1,   1,   1,   2,   1,   1,   1,   1,   1,   1,   1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
    1,   3,   1,   1,   1,   1,   1,   3,   1,   3,   1,   1,   1,   1,   1,   1,   1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
};


typedef enum UTF8State { UTF8_ACCEPT = 0, UTF8_REJECT = 1 } UTF8State;

uint32_t
decode_utf8(UTF8State *state, uint32_t *codep, uint8_t byte) {
    uint32_t type = utf8_data[byte];

    *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte);

    *state = (UTF8State)utf8_data[256 + *state * 16 + type];
    return *state;
}


static void
populate_icu_string(const char *text, int text_sz, icu::UnicodeString &str, std::vector<int> &byte_offsets) {
    UTF8State state = UTF8_ACCEPT, prev = UTF8_ACCEPT;
    uint32_t codep = 0;
    for (int i = 0, pos = 0; i < text_sz; i++) {
        switch (decode_utf8(&state, &codep, text[i])) {
            case UTF8_ACCEPT: {
                size_t sz = str.length();
                str.append((UChar32)codep);
                sz = str.length() - sz;
                for (size_t x = 0; x < sz; x++) byte_offsets.push_back(pos);
                pos = i + 1;
            } break;
            case UTF8_REJECT:
                state = UTF8_ACCEPT;
                if (prev != UTF8_ACCEPT && i > 0) i--;
                break;
        }
        prev = state;
    }
    byte_offsets.push_back(text_sz);
}
// }}}

static char ui_language[16] = {'e', 'n', 0};
static std::mutex global_mutex;

class IteratorDescription {
  public:
    const char *language;
    UScriptCode script;
};

class Stemmer {
  private:
    struct sb_stemmer *handle;
    char lang_name[32];

  public:
    Stemmer() : handle(NULL), lang_name() {}
    Stemmer(const char *lang) {
        size_t len = strlen(lang);
        for (size_t i = 0; i < sizeof(lang_name) - 1 && i < len; i++) {
            lang_name[i] = lang[i];
            if ('A' <= lang_name[i] && lang_name[i] <= 'Z') lang_name[i] += 'a' - 'A';
        }
        lang_name[std::min(len, sizeof(lang_name) - 1)] = 0;
        handle = sb_stemmer_new(lang_name, NULL);
    }
    const char *
    language_name() const {
        return lang_name;
    }

    ~Stemmer() {
        if (handle) {
            sb_stemmer_delete(handle);
            handle = NULL;
        }
    }

    const char *
    stem(const char *token, size_t token_sz, int *sz) {
        const char *ans = NULL;
        *sz = 0;
        if (handle) {
            ans = reinterpret_cast<const char *>(sb_stemmer_stem(handle, reinterpret_cast<const sb_symbol *>(token), (int)token_sz));
            if (ans) *sz = sb_stemmer_length(handle);
        }
        return ans;
    }

    explicit
    operator bool() const noexcept {
        return handle != NULL;
    }
};

typedef std::unique_ptr<icu::BreakIterator> BreakIterator;
typedef std::unique_ptr<Stemmer> StemmerPtr;
static const std::string empty_string("");

class Tokenizer {
  private:
    bool remove_diacritics, stem_words;
    std::unique_ptr<icu::Transliterator> diacritics_remover;
    std::vector<int> byte_offsets;
    std::string token_buf, current_ui_language;
    token_callback_func current_callback;
    void *current_callback_ctx;
    std::unordered_map<std::string, BreakIterator> iterators;
    std::unordered_map<std::string, StemmerPtr> stemmers;

    bool
    is_token_char(UChar32 ch) const {
        switch (u_charType(ch)) {
            case U_UPPERCASE_LETTER:
            case U_LOWERCASE_LETTER:
            case U_TITLECASE_LETTER:
            case U_MODIFIER_LETTER:
            case U_OTHER_LETTER:
            case U_DECIMAL_DIGIT_NUMBER:
            case U_LETTER_NUMBER:
            case U_OTHER_NUMBER:
            case U_CURRENCY_SYMBOL:
            case U_OTHER_SYMBOL:
            case U_PRIVATE_USE_CHAR: return true;
            default: break; ;
        }
        return false;
    }

    int
    send_token(const icu::UnicodeString &token, int32_t start_offset, int32_t end_offset, StemmerPtr &stemmer, int flags = 0) {
        token_buf.clear();
        token_buf.reserve(4 * token.length());
        token.toUTF8String(token_buf);
        const char *root = token_buf.c_str();
        int sz = (int)token_buf.size();
        if (stem_words && stemmer->operator bool()) {
            root = stemmer->stem(root, sz, &sz);
            if (!root) {
                root = token_buf.c_str();
                sz = (int)token_buf.size();
            }
        }
        return current_callback(current_callback_ctx, flags, root, (int)sz, byte_offsets.at(start_offset), byte_offsets.at(end_offset));
    }

    const char *
    iterator_language_for_script(UScriptCode script) const {
        switch (script) {
            default: return "";
            case USCRIPT_THAI:
            case USCRIPT_LAO: return "th_TH";
            case USCRIPT_KHMER: return "km_KH";
            case USCRIPT_MYANMAR: return "my_MM";
            case USCRIPT_HIRAGANA:
            case USCRIPT_KATAKANA: return "ja_JP";
            case USCRIPT_HANGUL: return "ko_KR";
            case USCRIPT_HAN:
            case USCRIPT_SIMPLIFIED_HAN:
            case USCRIPT_TRADITIONAL_HAN:
            case USCRIPT_HAN_WITH_BOPOMOFO: return "zh";
        }
    }

    bool
    at_script_boundary(IteratorDescription &current, UChar32 next_codepoint) const {
        icu::ErrorCode err;
        UScriptCode script = uscript_getScript(next_codepoint, err);
        if (script == USCRIPT_COMMON || script == USCRIPT_INVALID_CODE || script == USCRIPT_INHERITED || current.script == script) return false;
        const char *lang = iterator_language_for_script(script);
        if (strcmp(current.language, lang) == 0) return false;
        current.script = script;
        current.language = lang;
        return true;
    }

    void
    ensure_basic_iterator(void) {
        std::lock_guard<std::mutex> lock(global_mutex);
        if (current_ui_language != ui_language || iterators.find(empty_string) == iterators.end()) {
            current_ui_language.clear();
            current_ui_language = ui_language;
            icu::ErrorCode status;
            if (current_ui_language.empty()) {
                iterators[empty_string] = BreakIterator(icu::BreakIterator::createWordInstance(icu::Locale::getDefault(), status));
            } else {
                ensure_lang_iterator(ui_language);
            }
        }
    }

    BreakIterator &
    ensure_lang_iterator(const char *lang = "") {
        auto ans = iterators.find(lang);
        if (ans == iterators.end()) {
            icu::ErrorCode status;
            iterators[lang] = BreakIterator(icu::BreakIterator::createWordInstance(icu::Locale::createCanonical(lang), status));
            if (status.isFailure()) { iterators[lang] = BreakIterator(icu::BreakIterator::createWordInstance(icu::Locale::getDefault(), status)); }
            ans = iterators.find(lang);
        }
        return ans->second;
    }

    StemmerPtr &
    ensure_stemmer(const char *lang = "") {
        if (!lang[0]) lang = current_ui_language.c_str();
        auto ans = stemmers.find(lang);
        if (ans == stemmers.end()) {
            stemmers[lang] = stem_words ? std::make_unique<Stemmer>(lang) : std::make_unique<Stemmer>();
            ans = stemmers.find(lang);
        }
        return ans->second;
    }

    int
    tokenize_script_block(
        const icu::UnicodeString &str,
        int32_t block_start,
        int32_t block_limit,
        bool for_query_or_aux,
        token_callback_func callback,
        void *callback_ctx,
        BreakIterator &word_iterator,
        StemmerPtr &stemmer) {
        word_iterator->setText(str.tempSubStringBetween(block_start, block_limit));
        int32_t token_start_pos = word_iterator->first() + block_start, token_end_pos;
        int rc = SQLITE_OK;
        do {
            token_end_pos = word_iterator->next();
            if (token_end_pos == icu::BreakIterator::DONE) token_end_pos = block_limit;
            else token_end_pos += block_start;
            if (token_end_pos > token_start_pos) {
                bool is_token = false;
                for (int32_t pos = token_start_pos; !is_token && pos < token_end_pos; pos = str.moveIndex32(pos, 1)) {
                    if (is_token_char(str.char32At(pos))) is_token = true;
                }
                if (is_token) {
                    icu::UnicodeString token(str, token_start_pos, token_end_pos - token_start_pos);
                    token.foldCase();
                    if ((rc = send_token(token, token_start_pos, token_end_pos, stemmer)) != SQLITE_OK) return rc;
                    if (!for_query_or_aux && remove_diacritics) {
                        icu::UnicodeString tt(str, token_start_pos, token_end_pos - token_start_pos);
                        diacritics_remover->transliterate(tt);
                        tt.foldCase();
                        if (tt != token) {
                            if ((rc = send_token(tt, token_start_pos, token_end_pos, stemmer, FTS5_TOKEN_COLOCATED)) != SQLITE_OK) return rc;
                        }
                    }
                }
            }
            token_start_pos = token_end_pos;
        } while (token_end_pos < block_limit);
        return rc;
    }

  public:
    int constructor_error;

    Tokenizer(const char **args, int nargs, bool stem_words = false)
        : remove_diacritics(true), stem_words(stem_words), diacritics_remover(), byte_offsets(), token_buf(), current_ui_language(""), current_callback(NULL),
          current_callback_ctx(NULL), iterators(), stemmers(),

          constructor_error(SQLITE_OK) {
        for (int i = 0; i < nargs; i++) {
            if (strcmp(args[i], "remove_diacritics") == 0) {
                i++;
                if (i < nargs && strcmp(args[i], "0") == 0) remove_diacritics = false;
            } else if (strcmp(args[i], "stem_words") == 0) {
                i++;
                if (i < nargs && strcmp(args[i], "0") == 0) stem_words = false;
                else stem_words = true;
            }
        }
        if (remove_diacritics) {
            icu::ErrorCode status;
            diacritics_remover.reset(icu::Transliterator::createInstance("NFD; [:M:] Remove; NFC", UTRANS_FORWARD, status));
            if (status.isFailure()) {
                fprintf(stderr, "Failed to create ICU transliterator to remove diacritics with error: %s\n", status.errorName());
                constructor_error = SQLITE_INTERNAL;
                diacritics_remover.reset(NULL);
                remove_diacritics = false;
            }
        }
        std::lock_guard<std::mutex> lock(global_mutex);
        current_ui_language = ui_language;
    }

    int
    tokenize(void *callback_ctx, int flags, const char *text, int text_sz, token_callback_func callback) {
        ensure_basic_iterator();
        current_callback = callback;
        current_callback_ctx = callback_ctx;
        icu::UnicodeString str(text_sz, 0, 0);
        byte_offsets.clear();
        byte_offsets.reserve(text_sz + 8);
        populate_icu_string(text, text_sz, str, byte_offsets);
        int32_t offset = str.getChar32Start(0);
        int rc = SQLITE_OK;
        const bool for_query_or_aux = (flags & (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_AUX)) != 0;
        IteratorDescription state;
        state.language = "";
        state.script = USCRIPT_COMMON;
        int32_t start_script_block_at = offset;
        auto word_iterator = std::ref(ensure_lang_iterator(state.language));
        auto stemmer = std::ref(ensure_stemmer(state.language));
        while (offset < str.length()) {
            UChar32 ch = str.char32At(offset);
            if (at_script_boundary(state, ch)) {
                if (offset > start_script_block_at) {
                    if ((rc = tokenize_script_block(str, start_script_block_at, offset, for_query_or_aux, callback, callback_ctx, word_iterator, stemmer)) !=
                        SQLITE_OK)
                        return rc;
                }
                start_script_block_at = offset;
                word_iterator = ensure_lang_iterator(state.language);
                stemmer = ensure_stemmer(state.language);
            }
            offset = str.moveIndex32(offset, 1);
        }
        if (offset > start_script_block_at) {
            rc = tokenize_script_block(str, start_script_block_at, offset, for_query_or_aux, callback, callback_ctx, word_iterator, stemmer);
        }
        return rc;
    }
};

// boilerplate {{{
static int
fts5_api_from_db(sqlite3 *db, fts5_api **ppApi) {
    sqlite3_stmt *pStmt = 0;
    *ppApi = 0;
    int rc = sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0);
    if (rc == SQLITE_OK) {
        sqlite3_bind_pointer(pStmt, 1, reinterpret_cast<void *>(ppApi), "fts5_api_ptr", 0);
        (void)sqlite3_step(pStmt);
        rc = sqlite3_finalize(pStmt);
    }
    return rc;
}

static int
_tok_create(void *sqlite3, const char **azArg, int nArg, Fts5Tokenizer **ppOut, bool stem_words = false) {
    int rc = SQLITE_OK;
    try {
        Tokenizer *p = new Tokenizer(azArg, nArg, stem_words);
        if (p->constructor_error != SQLITE_OK) {
            rc = p->constructor_error;
            delete p;
        } else {
            *ppOut = reinterpret_cast<Fts5Tokenizer *>(p);
        }
    } catch (std::bad_alloc const &) { return SQLITE_NOMEM; } catch (...) {
        return SQLITE_ERROR;
    }
    return rc;
}

static int
tok_create(void *sqlite3, const char **azArg, int nArg, Fts5Tokenizer **ppOut) {
    return _tok_create(sqlite3, azArg, nArg, ppOut);
}

static int
tok_create_with_stemming(void *sqlite3, const char **azArg, int nArg, Fts5Tokenizer **ppOut) {
    return _tok_create(sqlite3, azArg, nArg, ppOut, true);
}

static int
tok_tokenize(Fts5Tokenizer *tokenizer_ptr, void *callback_ctx, int flags, const char *text, int text_sz, token_callback_func callback) {
    Tokenizer *p = reinterpret_cast<Tokenizer *>(tokenizer_ptr);
    try {
        return p->tokenize(callback_ctx, flags, text, text_sz, callback);
    } catch (std::bad_alloc const &) { return SQLITE_NOMEM; } catch (...) {
        return SQLITE_ERROR;
    }
}

static void
tok_delete(Fts5Tokenizer *p) {
    Tokenizer *t = reinterpret_cast<Tokenizer *>(p);
    delete t;
}

extern "C" {
#ifdef _MSC_VER
#define MYEXPORT __declspec(dllexport)
#else
#define MYEXPORT __attribute__((visibility("default")))
#endif

MYEXPORT int
calibre_sqlite_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
    SQLITE_EXTENSION_INIT2(pApi);
    fts5_api *fts5api = NULL;
    int rc = fts5_api_from_db(db, &fts5api);
    if (rc != SQLITE_OK) {
        *pzErrMsg = (char *)"Failed to get FTS 5 API with error code";
        return rc;
    }
    if (!fts5api || fts5api->iVersion < 2) {
        *pzErrMsg = (char *)"FTS 5 iVersion too old or NULL pointer";
        return SQLITE_ERROR;
    }
    fts5_tokenizer tok = {tok_create, tok_delete, tok_tokenize};
    fts5api->xCreateTokenizer(fts5api, "unicode61", reinterpret_cast<void *>(fts5api), &tok, NULL);
    fts5api->xCreateTokenizer(fts5api, "calibre", reinterpret_cast<void *>(fts5api), &tok, NULL);
    fts5_tokenizer tok2 = {tok_create_with_stemming, tok_delete, tok_tokenize};
    fts5api->xCreateTokenizer(fts5api, "porter", reinterpret_cast<void *>(fts5api), &tok2, NULL);
    return SQLITE_OK;
}

MYEXPORT int
sqlite3_sqliteextension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
    return calibre_sqlite_extension_init(db, pzErrMsg, pApi);
}
}

static PyObject *
get_locales_for_break_iteration(PyObject *self, PyObject *args) {
    std::unique_ptr<icu::StringEnumeration> locs(icu::BreakIterator::getAvailableLocales());
    icu::ErrorCode status;
    pyobject_raii ans(PyList_New(0));
    if (ans) {
        const icu::UnicodeString *item;
        while ((item = locs->snext(status))) {
            std::string name;
            item->toUTF8String(name);
            pyobject_raii pn(PyUnicode_FromString(name.c_str()));
            if (pn) PyList_Append(ans.ptr(), pn.ptr());
        }
        if (status.isFailure()) {
            PyErr_Format(PyExc_RuntimeError, "Failed to iterate over locales with error: %s", status.errorName());
            return NULL;
        }
    }
    return ans.detach();
}

static PyObject *
set_ui_language(PyObject *self, PyObject *args) {
    std::lock_guard<std::mutex> lock(global_mutex);
    const char *val;
    if (!PyArg_ParseTuple(args, "s", &val)) return NULL;
    strncpy(ui_language, val, sizeof(ui_language) - 1);
    Py_RETURN_NONE;
}

static int
py_callback(void *ctx, int flags, const char *text, int text_length, int start_offset, int end_offset) {
    PyObject *ans = reinterpret_cast<PyObject *>(ctx);
    Py_ssize_t psz = text_length;
    pyobject_raii item(Py_BuildValue("{ss# si si si}", "text", text, psz, "start", start_offset, "end", end_offset, "flags", flags));
    if (item) PyList_Append(ans, item.ptr());
    return SQLITE_OK;
}

static PyObject *
tokenize(PyObject *self, PyObject *args) {
    const char *text;
    Py_ssize_t text_length, remove_diacritics = 1, flags = FTS5_TOKENIZE_DOCUMENT;
    if (!PyArg_ParseTuple(args, "s#|pi", &text, &text_length, &remove_diacritics, &flags)) return NULL;
    const char *targs[2] = {"remove_diacritics", "2"};
    if (!remove_diacritics) targs[1] = "0";
    Tokenizer t(targs, sizeof(targs) / sizeof(targs[0]));
    pyobject_raii ans(PyList_New(0));
    if (!ans) return NULL;
    t.tokenize(ans.ptr(), flags, text, text_length, py_callback);
    return ans.detach();
}

static PyObject *
stem(PyObject *self, PyObject *args) {
    const char *text, *lang = "en";
    Py_ssize_t text_length;
    if (!PyArg_ParseTuple(args, "s#|s", &text, &text_length, &lang)) return NULL;
    Stemmer s(lang);
    if (!s) {
        PyErr_SetString(PyExc_ValueError, "No stemmer for the specified language");
        return NULL;
    }
    int sz;
    const char *result = s.stem(text, text_length, &sz);
    Py_ssize_t a = sz;
    if (!result) return PyErr_NoMemory();
    return Py_BuildValue("s#", result, a);
}

static PyMethodDef methods[] = {
    {"get_locales_for_break_iteration", get_locales_for_break_iteration, METH_NOARGS, "Get list of available locales for break iteration"},
    {"set_ui_language", set_ui_language, METH_VARARGS, "Set the current UI language"},
    {"tokenize", tokenize, METH_VARARGS, "Tokenize a string, useful for testing"},
    {"stem", stem, METH_VARARGS, "Stem a word in the specified language, defaulting to English"},
    {NULL, NULL, 0, NULL}};

static int
exec_module(PyObject *mod) {
    if (PyModule_AddIntMacro(mod, FTS5_TOKENIZE_QUERY) != 0) return 1;
    if (PyModule_AddIntMacro(mod, FTS5_TOKENIZE_DOCUMENT) != 0) return 1;
    if (PyModule_AddIntMacro(mod, FTS5_TOKENIZE_PREFIX) != 0) return 1;
    if (PyModule_AddIntMacro(mod, FTS5_TOKENIZE_AUX) != 0) return 1;
    if (PyModule_AddIntMacro(mod, FTS5_TOKEN_COLOCATED) != 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};

extern "C" {
CALIBRE_MODINIT_FUNC
PyInit_sqlite_extension(void) {
    module_def.m_name = "sqlite_extension";
    module_def.m_doc = "Implement ICU based tokenizer for FTS5";
    module_def.m_methods = methods;
    module_def.m_slots = slots;
    return PyModuleDef_Init(&module_def);
}
} // }}}
