#!/usr/bin/env python
"""
CFFI builder script for LMDB extension module.

This script uses the modern CFFI API (ffi.set_source) to define the
lmdb_cffi extension module for setuptools integration.

Used by setuptools via cffi_modules configuration in pyproject.toml.
"""

import os
import sys

# Import the configuration generated by build_lmdb.py
# Avoid circular import by importing _config directly
_config_vars = None
try:
    # Try importing _config module directly without triggering zlmdb/lmdb/__init__.py
    import importlib.util
    spec = importlib.util.spec_from_file_location("_config", "zlmdb/lmdb/_config.py")
    if spec and spec.loader:
        _config = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(_config)
        _config_vars = _config.CONFIG
except (FileNotFoundError, ImportError, AttributeError):
    pass

if _config_vars is None:
    # Fallback if _config.py doesn't exist yet
    print("WARNING: zlmdb/lmdb/_config.py not found, using minimal config")
    _config_vars = {
        'extra_compile_args': ['-UNDEBUG', '-DHAVE_PATCHED_LMDB=1', '-w'],
        'extra_sources': ['build/lmdb-src/mdb.c', 'build/lmdb-src/midl.c'],
        'extra_include_dirs': ['build/lmdb-src', 'lmdb-patches'],
        'extra_library_dirs': [],
        'libraries': [],
    }

# Check if we have patched LMDB
_have_patched_lmdb = '-DHAVE_PATCHED_LMDB=1' in _config_vars['extra_compile_args']


# CFFI C definitions (from lmdb/cffi.py)
_CFFI_CDEF = '''
    typedef int mode_t;
    typedef ... MDB_env;
    typedef struct MDB_txn MDB_txn;
    typedef struct MDB_cursor MDB_cursor;
    typedef unsigned int MDB_dbi;

    typedef struct MDB_val {
        size_t mv_size;
        void *mv_data;
    } MDB_val;

    typedef int (*MDB_cmp_func)(const MDB_val *a, const MDB_val *b);
    typedef void (*MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr,
        void *relctx);

    typedef enum MDB_cursor_op {
        MDB_FIRST,
        MDB_FIRST_DUP,
        MDB_GET_BOTH,
        MDB_GET_BOTH_RANGE,
        MDB_GET_CURRENT,
        MDB_GET_MULTIPLE,
        MDB_LAST,
        MDB_LAST_DUP,
        MDB_NEXT,
        MDB_NEXT_DUP,
        MDB_NEXT_MULTIPLE,
        MDB_NEXT_NODUP,
        MDB_PREV,
        MDB_PREV_DUP,
        MDB_PREV_NODUP,
        MDB_SET,
        MDB_SET_KEY,
        MDB_SET_RANGE,
        MDB_PREV_MULTIPLE,
        ...
    } MDB_cursor_op;

    typedef struct MDB_stat {
        unsigned int ms_psize;
        unsigned int ms_depth;
        size_t ms_branch_pages;
        size_t ms_leaf_pages;
        size_t ms_overflow_pages;
        size_t ms_entries;
    } MDB_stat;

    typedef struct MDB_envinfo {
        void *me_mapaddr;
        size_t me_mapsize;
        size_t me_last_pgno;
        size_t me_last_txnid;
        unsigned int me_maxreaders;
        unsigned int me_numreaders;
    } MDB_envinfo;

    char *mdb_version(int *major, int *minor, int *patch);
    char *mdb_strerror(int err);

    int mdb_env_create(MDB_env **env);
    int mdb_env_open(MDB_env *env, const char *path, unsigned int flags,
        mode_t mode);
    int mdb_env_copy2(MDB_env *env, const char *path, unsigned int flags);
    int mdb_env_copyfd2(MDB_env *env, int fd, unsigned int flags);
    int mdb_env_stat(MDB_env *env, MDB_stat *stat);
    int mdb_env_info(MDB_env *env, MDB_envinfo *stat);
    int mdb_env_sync(MDB_env *env, int force);
    void mdb_env_close(MDB_env *env);
    int mdb_env_set_flags(MDB_env *env, unsigned int flags, int onoff);
    int mdb_env_get_flags(MDB_env *env, unsigned int *flags);
    int mdb_env_get_path(MDB_env *env, const char **path);
    int mdb_env_get_fd(MDB_env *env, int *fd);
    int mdb_env_set_mapsize(MDB_env *env, size_t size);
    int mdb_env_set_maxreaders(MDB_env *env, unsigned int readers);
    int mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers);
    int mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs);
    int mdb_env_get_maxkeysize(MDB_env *env);
    int mdb_env_set_userctx(MDB_env *env, void *ctx);
    void *mdb_env_get_userctx(MDB_env *env);

    typedef void (*MDB_assert_func)(MDB_env *env, const char *msg);
    int mdb_env_set_assert(MDB_env *env, MDB_assert_func func);

    typedef int (*MDB_msg_func)(const char *msg, void *ctx);

    int mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags,
        MDB_txn **txn);
    MDB_env *mdb_txn_env(MDB_txn *txn);
    size_t mdb_txn_id(MDB_txn *txn);
    int mdb_txn_commit(MDB_txn *txn);
    void mdb_txn_abort(MDB_txn *txn);
    void mdb_txn_reset(MDB_txn *txn);
    int mdb_txn_renew(MDB_txn *txn);

    int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags,
        MDB_dbi *dbi);
    int mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *stat);
    int mdb_dbi_flags(MDB_txn *txn, MDB_dbi dbi, unsigned int *flags);
    void mdb_dbi_close(MDB_env *env, MDB_dbi dbi);
    int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del);

    int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func cmp);
    int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func cmp);
    int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func rel);
    int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx);

    int mdb_get(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
    int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data,
        unsigned int flags);
    int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);

    int mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **cursor);
    void mdb_cursor_close(MDB_cursor *cursor);
    int mdb_cursor_renew(MDB_txn *txn, MDB_cursor *cursor);
    MDB_txn *mdb_cursor_txn(MDB_cursor *cursor);
    MDB_dbi mdb_cursor_dbi(MDB_cursor *cursor);
    int mdb_cursor_get(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
        MDB_cursor_op op);
    int mdb_cursor_put(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
        unsigned int flags);
    int mdb_cursor_del(MDB_cursor *cursor, unsigned int flags);
    int mdb_cursor_count(MDB_cursor *cursor, size_t *countp);

    int mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b);
    int mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b);

    int mdb_reader_list(MDB_env *env, MDB_msg_func func, void *ctx);
    int mdb_reader_check(MDB_env *env, int *dead);

    // LMDB constants for full py-lmdb API compatibility
    // Using ... tells CFFI to extract actual values from lmdb.h automatically

    // Version information
    #define MDB_VERSION_MAJOR ...
    #define MDB_VERSION_MINOR ...
    #define MDB_VERSION_PATCH ...

    // Environment flags (for mdb_env_open)
    #define MDB_FIXEDMAP ...
    #define MDB_NOSUBDIR ...
    #define MDB_RDONLY ...
    #define MDB_WRITEMAP ...
    #define MDB_NOMETASYNC ...
    #define MDB_NOSYNC ...
    #define MDB_MAPASYNC ...
    #define MDB_NOTLS ...
    #define MDB_NOLOCK ...
    #define MDB_NORDAHEAD ...
    #define MDB_NOMEMINIT ...

    // Database flags (for mdb_dbi_open)
    #define MDB_REVERSEKEY ...
    #define MDB_DUPSORT ...
    #define MDB_INTEGERKEY ...
    #define MDB_DUPFIXED ...
    #define MDB_INTEGERDUP ...
    #define MDB_REVERSEDUP ...
    #define MDB_CREATE ...

    // Write flags (for mdb_put)
    #define MDB_NOOVERWRITE ...
    #define MDB_NODUPDATA ...
    #define MDB_CURRENT ...
    #define MDB_RESERVE ...
    #define MDB_APPEND ...
    #define MDB_APPENDDUP ...

    // Copy flags (for mdb_env_copy2)
    #define MDB_CP_COMPACT ...

    // Error codes (LMDB-specific)
    #define MDB_KEYEXIST ...
    #define MDB_NOTFOUND ...
    #define MDB_PAGE_NOTFOUND ...
    #define MDB_CORRUPTED ...
    #define MDB_PANIC ...
    #define MDB_VERSION_MISMATCH ...
    #define MDB_INVALID ...
    #define MDB_MAP_FULL ...
    #define MDB_DBS_FULL ...
    #define MDB_READERS_FULL ...
    #define MDB_TLS_FULL ...
    #define MDB_TXN_FULL ...
    #define MDB_CURSOR_FULL ...
    #define MDB_PAGE_FULL ...
    #define MDB_MAP_RESIZED ...
    #define MDB_INCOMPATIBLE ...
    #define MDB_BAD_RSLOT ...
    #define MDB_BAD_TXN ...
    #define MDB_BAD_VALSIZE ...
    #define MDB_BAD_DBI ...

    // POSIX error codes (from errno.h, used by py-lmdb error handling)
    #define EACCES ...
    #define EAGAIN ...
    #define EINVAL ...
    #define ENOMEM ...
    #define ENOSPC ...

    // py-lmdb helper functions (inline MDB_val construction for performance)
    static int pymdb_del(MDB_txn *txn, MDB_dbi dbi,
                         char *key_s, size_t keylen,
                         char *val_s, size_t vallen);
    static int pymdb_put(MDB_txn *txn, MDB_dbi dbi,
                         char *key_s, size_t keylen,
                         char *val_s, size_t vallen,
                         unsigned int flags);
    static int pymdb_get(MDB_txn *txn, MDB_dbi dbi,
                         char *key_s, size_t keylen,
                         MDB_val *val_out);
    static int pymdb_cursor_get(MDB_cursor *cursor,
                                char *key_s, size_t key_len,
                                char *data_s, size_t data_len,
                                MDB_val *key, MDB_val *data, int op);
    static int pymdb_cursor_put(MDB_cursor *cursor,
                                char *key_s, size_t keylen,
                                char *val_s, size_t vallen, int flags);

    // Memory prefaulting helper
    static void preload(int rc, void *x, size_t size);
'''

# Patched LMDB extensions
_CFFI_CDEF_PATCHED = '''
    int mdb_env_copy3(MDB_env *env, const char *path, unsigned int flags, MDB_txn *txn);
    int mdb_env_copyfd3(MDB_env *env, int fd, unsigned int flags, MDB_txn *txn);
'''

# Combine definitions if we have patched LMDB
if _have_patched_lmdb:
    _CFFI_CDEF += _CFFI_CDEF_PATCHED

# CFFI C code (includes helper function implementations from py-lmdb)
_CFFI_VERIFY = '''
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <errno.h>
    #include "lmdb.h"
    #include "preload.h"

    // py-lmdb helper functions (inline MDB_val construction for CPython performance)
    static int pymdb_get(MDB_txn *txn, MDB_dbi dbi, char *key_s, size_t keylen,
                         MDB_val *val_out)
    {
        MDB_val key = {keylen, key_s};
        int rc = mdb_get(txn, dbi, &key, val_out);
        return rc;
    }

    static int pymdb_put(MDB_txn *txn, MDB_dbi dbi, char *key_s, size_t keylen,
                         char *val_s, size_t vallen, unsigned int flags)
    {
        MDB_val key = {keylen, key_s};
        MDB_val val = {vallen, val_s};
        return mdb_put(txn, dbi, &key, &val, flags);
    }

    static int pymdb_del(MDB_txn *txn, MDB_dbi dbi, char *key_s, size_t keylen,
                         char *val_s, size_t vallen)
    {
        MDB_val key = {keylen, key_s};
        MDB_val val = {vallen, val_s};
        MDB_val *valptr;
        if(vallen == 0) {
            valptr = NULL;
        } else {
            valptr = &val;
        }
        return mdb_del(txn, dbi, &key, valptr);
    }

    static int pymdb_cursor_get(MDB_cursor *cursor,
                                char *key_s, size_t key_len,
                                char *data_s, size_t data_len,
                                MDB_val *key, MDB_val *data, int op)
    {
        MDB_val tmp_key = {key_len, key_s};
        MDB_val tmp_data = {data_len, data_s};
        int rc = mdb_cursor_get(cursor, &tmp_key, &tmp_data, op);
        if(! rc) {
            *key = tmp_key;
            *data = tmp_data;
        }
        return rc;
    }

    static int pymdb_cursor_put(MDB_cursor *cursor, char *key_s, size_t keylen,
                                char *val_s, size_t vallen, int flags)
    {
        MDB_val tmpkey = {keylen, key_s};
        MDB_val tmpval = {vallen, val_s};
        return mdb_cursor_put(cursor, &tmpkey, &tmpval, flags);
    }
'''


# Create FFI instance
import cffi
ffi = cffi.FFI()
ffi.cdef(_CFFI_CDEF)

# Set source using modern CFFI API
ffi.set_source(
    "zlmdb.lmdb._lmdb_cffi",
    _CFFI_VERIFY,
    sources=_config_vars['extra_sources'],
    include_dirs=_config_vars['extra_include_dirs'],
    extra_compile_args=_config_vars['extra_compile_args'],
    libraries=_config_vars['libraries'],
    library_dirs=_config_vars['extra_library_dirs'],
)

# This script exports 'ffi' for setuptools cffi_modules
if __name__ == '__main__':
    ffi.compile(verbose=True)
