/*
 * Copyright 2022, The Cozo Project Authors.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file,
 * You can obtain one at https://mozilla.org/MPL/2.0/.
 */
#![warn(rust_2018_idioms, future_incompatible)]
#![allow(clippy::missing_safety_doc)]

use std::collections::BTreeMap;
use std::ffi::{c_char, CStr, CString};
use std::ptr::null_mut;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;

use lazy_static::lazy_static;

use cozo::*;

struct Handles {
    current: AtomicI32,
    dbs: Mutex<BTreeMap<i32, DbInstance>>,
}

lazy_static! {
    static ref HANDLES: Handles = Handles {
        current: Default::default(),
        dbs: Mutex::new(Default::default())
    };
}

/// Open a database.
///
/// `engine`:  which storage engine to use, can be "mem", "sqlite" or "rocksdb".
/// `path`:    should contain the UTF-8 encoded path name as a null-terminated C-string.
/// `db_id`:   will contain the ID of the database opened.
/// `options`: options for the DB constructor: engine dependent.
///
/// When the function is successful, null pointer is returned,
/// otherwise a pointer to a C-string containing the error message will be returned.
/// The returned C-string must be freed with `cozo_free_str`.
#[no_mangle]
pub unsafe extern "C" fn cozo_open_db(
    engine: *const c_char,
    path: *const c_char,
    options: *const c_char,
    db_id: &mut i32,
) -> *mut c_char {
    let engine = match CStr::from_ptr(engine).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };

    let path = match CStr::from_ptr(path).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };

    let options = match CStr::from_ptr(options).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };

    let db = match DbInstance::new_with_str(engine, path, options) {
        Ok(db) => db,
        Err(err) => return CString::new(err).unwrap().into_raw(),
    };

    let id = HANDLES.current.fetch_add(1, Ordering::AcqRel);
    let mut dbs = HANDLES.dbs.lock().unwrap();
    dbs.insert(id, db);
    *db_id = id;
    null_mut()
}

/// Close a database.
///
/// `db_id`: the ID representing the database to close.
///
/// Returns `true` if the database is closed,
/// `false` if it has already been closed, or does not exist.
#[no_mangle]
pub unsafe extern "C" fn cozo_close_db(db_id: i32) -> bool {
    let db = {
        let mut dbs = HANDLES.dbs.lock().unwrap();
        dbs.remove(&db_id)
    };
    db.is_some()
}

/// Run query against a database.
///
/// `db_id`:           the ID representing the database to run the query.
/// `script_raw`:      a UTF-8 encoded C-string for the CozoScript to execute.
/// `params_raw`:      a UTF-8 encoded C-string for the params of the query,
///                    in JSON format. You must always pass in a valid JSON map,
///                    even if you do not use params in your query
///                    (pass "{}" in this case).
/// `immutable_query`: whether the query is read-only.
///
/// Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`.
/// The string contains the JSON return value of the query.
#[no_mangle]
pub unsafe extern "C" fn cozo_run_query(
    db_id: i32,
    script_raw: *const c_char,
    params_raw: *const c_char,
    immutable_query: bool,
) -> *mut c_char {
    let script = match CStr::from_ptr(script_raw).to_str() {
        Ok(p) => p,
        Err(_) => {
            return CString::new(r##"{"ok":false,"message":"script is not UTF-8 encoded"}"##)
                .unwrap()
                .into_raw();
        }
    };
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };
    let params_str = match CStr::from_ptr(params_raw).to_str() {
        Ok(p) => p,
        Err(_) => {
            return CString::new(
                r##"{"ok":false,"message":"params argument is not UTF-8 encoded"}"##,
            )
            .unwrap()
            .into_raw();
        }
    };

    let result = db.run_script_str(script, params_str, immutable_query);
    CString::new(result).unwrap().into_raw()
}

#[no_mangle]
/// Import data into relations
///
/// Note that triggers are _not_ run for the relations, if any exists.
/// If you need to activate triggers, use queries with parameters.
///
/// `db_id`:        the ID representing the database.
/// `json_payload`: a UTF-8 encoded JSON payload, in the same form as returned by exporting relations.
///
/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`.
pub unsafe extern "C" fn cozo_import_relations(
    db_id: i32,
    json_payload: *const c_char,
) -> *mut c_char {
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };
    let data = match CStr::from_ptr(json_payload).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };
    CString::new(db.import_relations_str(data))
        .unwrap()
        .into_raw()
}

#[no_mangle]
/// Export relations into JSON
///
/// `db_id`:        the ID representing the database.
/// `json_payload`: a UTF-8 encoded JSON payload, see the manual for the expected fields.
///
/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`.
pub unsafe extern "C" fn cozo_export_relations(
    db_id: i32,
    json_payload: *const c_char,
) -> *mut c_char {
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };
    let data = match CStr::from_ptr(json_payload).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };
    CString::new(db.export_relations_str(data))
        .unwrap()
        .into_raw()
}

#[no_mangle]
/// Backup the database.
///
/// `db_id`:    the ID representing the database.
/// `out_path`: path of the output file.
///
/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`.
pub unsafe extern "C" fn cozo_backup(db_id: i32, out_path: *const c_char) -> *mut c_char {
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };
    let data = match CStr::from_ptr(out_path).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };
    CString::new(db.backup_db_str(data)).unwrap().into_raw()
}

#[no_mangle]
/// Restore the database from a backup.
///
/// `db_id`:   the ID representing the database.
/// `in_path`: path of the input file.
///
/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`.
pub unsafe extern "C" fn cozo_restore(db_id: i32, in_path: *const c_char) -> *mut c_char {
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };
    let data = match CStr::from_ptr(in_path).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };
    CString::new(db.restore_backup_str(data))
        .unwrap()
        .into_raw()
}

#[no_mangle]
/// Import data into relations from a backup
///
/// Note that triggers are _not_ run for the relations, if any exists.
/// If you need to activate triggers, use queries with parameters.
///
/// `db_id`:        the ID representing the database.
/// `json_payload`: a UTF-8 encoded JSON payload: `{"path": ..., "relations": [...]}`
///
/// Returns a UTF-8-encoded C-string indicating the result that **must** be freed with `cozo_free_str`.
pub unsafe extern "C" fn cozo_import_from_backup(
    db_id: i32,
    json_payload: *const c_char,
) -> *mut c_char {
    let db = {
        let db_ref = {
            let dbs = HANDLES.dbs.lock().unwrap();
            dbs.get(&db_id).cloned()
        };
        match db_ref {
            None => {
                return CString::new(r##"{"ok":false,"message":"database closed"}"##)
                    .unwrap()
                    .into_raw();
            }
            Some(db) => db,
        }
    };

    let data = match CStr::from_ptr(json_payload).to_str() {
        Ok(p) => p,
        Err(err) => return CString::new(format!("{err}")).unwrap().into_raw(),
    };

    CString::new(db.import_from_backup_str(data))
        .unwrap()
        .into_raw()
}

/// Free any C-string returned from the Cozo C API.
/// Must be called exactly once for each returned C-string.
///
/// `s`: the C-string to free.
#[no_mangle]
pub unsafe extern "C" fn cozo_free_str(s: *mut c_char) {
    let _ = CString::from_raw(s);
}

Homonyms

warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/mir/src/lib.rs
soft3/foculus/src/lib.rs
cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
warriors/trisha/rs/lib.rs
cyb/shell/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
warriors/trisha/honeycrisp/lib.rs
neural/trident/src/lib.rs
soft3/crate/src/lib.rs
cyb/honeycrisp/src/lib.rs
cyb/prysm/rs/lib.rs
soft3/lens/src/lib.rs
soft3/tru/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/nox/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
neural/rs/dialect/src/lib.rs
soft3/lens/assayer/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/bbg/rs/src/lib.rs
neural/rs/sigil/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/link/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/lens/porphyry/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/strata/ext/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rs/codegen/src/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/tok/rs/src/lib.rs
soft3/conformance/rs/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rs/macros/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
neural/rs/core/src/lib.rs
soft3/strata/nebu/rs/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/interp/lib.rs
soft3/strata/compute/src/lib.rs
soft3/lens/binius/src/lib.rs
soft3/strata/proof/src/lib.rs
neural/eidos/rs/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
neural/rune/rs/subject/lib.rs
soft3/soma/kernel/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
neural/rune/rs/lex/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
neural/inf/rs/lex/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
neural/trident/editor/zed/src/lib.rs
warriors/trisha/.vendor/twenty-first/src/lib.rs
warriors/erga/rs/pool/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
soft3/radio/quinn/quinn-udp/src/lib.rs
warriors/trisha/.vendor/triton-vm/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
warriors/erga/rs/wallet/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
warriors/erga/rs/autolykos/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
warriors/trisha/.vendor/triton-constraint-circuit/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
warriors/trisha/.vendor/triton-air/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
warriors/trisha/.vendor/triton-isa/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
warriors/trisha/.vendor/triton-constraint-builder/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
soft3/radio/quinn/bench/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
warriors/erga/rs/rtable-bench/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
warriors/erga/rs/blake-bench/src/lib.rs
neural/inf/rs/parse/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
warriors/erga/rs/app/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
warriors/erga/rs/mine-bench/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
warriors/erga/rs/miner/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-python/src/lib.rs
neural/inf/rs/cozo/cozo-lib-swift/src/lib.rs
neural/inf/rs/cozo/cozorocks/src/lib.rs
neural/inf/rs/cozo/cozo-lib-java/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/src/lib.rs
neural/inf/rs/cozo/cozo-lib-nodejs/src/lib.rs
neural/inf/rs/cozo/cozo-core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-wasm/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/acpu/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/acpu/src/lib.rs

Graph