// This file must be included rather than linked, because it relies on error handling
// and logging macros that differ based on the context.

#include "common.h"


int init_lmdb(const char *db_dir, MDB_env **out_dbenv, MDB_dbi *out_dbi) {
    MDB_env *dbenv = NULL;
    MDB_dbi dbi = 0;

    // Initialize LMDB environment
    int rc = mdb_env_create(&dbenv);
    if (rc != MDB_SUCCESS) {
        SET_LMDB_ERROR("env create", rc);
        return -1;
    }
    
    const char *map_size_env = getenv("OLMDB_MAP_SIZE");
    size_t map_size = map_size_env ? (size_t)strtoull(map_size_env, NULL, 10)
                                   : (1ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL); // 1TB default
    rc = mdb_env_set_mapsize(dbenv, map_size);
    if (rc != MDB_SUCCESS) {
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("set map size", rc);
        return -1;
    }
    
    rc = mdb_env_set_maxreaders(dbenv, 196);
    if (rc != MDB_SUCCESS) {
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("set max readers", rc);
        return -1;
    }
    
    rc = mdb_env_open(dbenv, db_dir, MDB_NOTLS, 0664);
    if (rc != MDB_SUCCESS) {
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("env init", rc);
        return -1;
    }
    
    // Open the database (within a transaction)
    MDB_txn *wtxn;
    rc = mdb_txn_begin(dbenv, NULL, 0, &wtxn);
    if (rc != MDB_SUCCESS) {
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("dbi init txn begin", rc);
        return -1;
    }
    
    rc = mdb_dbi_open(wtxn, NULL, 0, &dbi);
    if (rc != MDB_SUCCESS) {
        mdb_txn_abort(wtxn);
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("dbi init", rc);
        return -1;
    }
    
    rc = mdb_txn_commit(wtxn);
    if (rc != MDB_SUCCESS) {
        mdb_env_close(dbenv);
        SET_LMDB_ERROR("dbi init txn commit", rc);
        return -1;
    }

    *out_dbenv = dbenv;
    *out_dbi = dbi;
    return 0;
}

// Positions `cursor` at the start of an iteration. The key passed in is the
// half-open boundary the cursor begins at:
//   forward: the inclusive lower bound -> first key >= in_key
//   reverse: the exclusive upper bound -> largest key strictly < in_key
// (`reverse` only ever begins from an exclusive upper bound, so an exact match
// is stepped over rather than returned.)
int place_cursor(MDB_cursor *cursor, MDB_val *key, MDB_val *value, int reverse) {
    MDB_val in_key;
    memcpy(&in_key, key, sizeof(MDB_val));

    int cursor_mode = in_key.mv_size > 0 ? MDB_SET_RANGE : (reverse ? MDB_LAST : MDB_FIRST);
    int rc = mdb_cursor_get(cursor, key, value, cursor_mode);

    if (in_key.mv_size > 0 && reverse) {
        if (rc == MDB_NOTFOUND) {
            // in_key is past the last record, so the largest key < in_key is the last item
            rc = mdb_cursor_get(cursor, key, value, MDB_LAST);
        } else if (rc == 0) {
            // SET_RANGE landed on the first key >= in_key; step back to the
            // largest key strictly less than the exclusive upper bound.
            rc = mdb_cursor_get(cursor, key, value, MDB_PREV);
        }
    }
    
    if (rc == MDB_NOTFOUND) {
        key->mv_data = NULL; // mark cursor as at the end of the database
        key->mv_size = 0;
        value->mv_data = NULL;
        value->mv_size = 0; 
        rc = 0; // This is not an error, just means no more items to read
    }
    return rc;
}

// A simple and very fast hash function for checksums
uint64_t checksum(const char *data, size_t len, uint64_t val) {
    if (!data) return 0;
    val ^= len;
    val *= CHECKSUM_PRIME;
    for (size_t i = 0; i < len; i++) {
        val ^= (uint8_t)data[i];
        val *= CHECKSUM_PRIME;
    }
    return val;
}


// Helper function to format binary data for safe logging
char* format_binary_data(const void* data, size_t len) {
    static char buffers[4][512]; // Multiple buffers for multiple calls in one LOG
    static int buffer_index = 0;
    
    char* buffer = buffers[buffer_index];
    buffer_index = (buffer_index + 1) % 4; // Rotate through buffers
    
    if (!data || len == 0) {
        strcpy(buffer, "(null)");
        return buffer;
    }
    
    const unsigned char* bytes = (const unsigned char*)data;
    char* pos = buffer;
    size_t remaining = sizeof(buffers[0]) - 1; // Leave space for null terminator
    
    for (size_t i = 0; i < len && remaining > 6; i++) {
        // Look ahead to see if we have a string of at least 2 printable chars
        size_t string_len = 0;
        while (i + string_len < len && 
               bytes[i + string_len] >= 32 && bytes[i + string_len] < 127 &&
               bytes[i + string_len] != '"') {
            string_len++;
        }
        
        if (string_len >= 2 && remaining > string_len + 3) {
            // Print as quoted string
            *pos++ = '"';
            remaining--;
            for (size_t j = 0; j < string_len; j++) {
                *pos++ = bytes[i + j];
                remaining--;
            }
            *pos++ = '"';
            remaining--;
            i += string_len - 1; // -1 because loop will increment
            
            // Add space if not at end
            if (i + 1 < len && remaining > 1) {
                *pos++ = ' ';
                remaining--;
            }
        } else {
            // Print as hex byte
            int written = snprintf(pos, remaining, "%02x", bytes[i]);
            if (written >= (int)remaining) break; // Not enough space
            pos += written;
            remaining -= written;
            
            // Add space if not at end
            if (i + 1 < len && remaining > 1) {
                *pos++ = ' ';
                remaining--;
            }
        }
    }
    
    if (remaining <= 6) {
        // Truncated - indicate this
        strcpy(pos - 3, "...");
    } else {
        *pos = '\0';
    }
    
    return buffer;
}
