//! Key-Value semantics.

use super::Database;
use super::slice::Slice;

use super::bytes::Bytes;
use super::error::Error;
use crate::binding::*;
use crate::options::{ReadOptions, WriteOptions, c_readoptions, c_writeoptions};
use libc::{c_char, size_t};
use std::ptr;

/// Key-Value-Access to the LevelDB instance, providing
/// a basic interface.
pub trait KV {
    /// get a value from the LevelDB instance.
    ///
    /// The passed key will be compared using the comparator.
    fn get<'a>(
        &self,
        options: ReadOptions<'a>,
        key: Slice,
    ) -> Result<Option<Slice<'static>>, Error>;

    /// put a binary value into the LevelDB instance.
    ///
    /// If the key is already present in the LevelDB instance, it will be overwritten.
    ///
    /// The passed key will be compared using the comparator.
    ///
    /// The LevelDB instance will be synced to disk if `options.sync == true`. This is
    /// NOT the default.
    fn put(&self, options: WriteOptions, key: Slice, value: Slice) -> Result<(), Error>;
    /// delete a value from the LevelDB instance.
    ///
    /// The passed key will be compared using the comparator.
    ///
    /// The LevelDB instance will be synced to disk if `options.sync == true`. This is
    /// NOT the default.
    fn delete(&self, options: WriteOptions, key: Slice) -> Result<(), Error>;
}

impl KV for Database {
    /// put a binary value into the LevelDB instance.
    ///
    /// If the key is already present in the LevelDB instance, it will be overwritten.
    ///
    /// The passed key will be compared using the comparator.
    ///
    /// The LevelDB instance will be synced to disk if `options.sync == true`. This is
    /// NOT the default.
    fn put(&self, options: WriteOptions, key: Slice, value: Slice) -> Result<(), Error> {
        unsafe {
            let k = key.as_bytes();
            let v = value.as_bytes();
            let mut error = ptr::null_mut();
            let c_writeoptions = c_writeoptions(options);
            leveldb_put(
                self.database.ptr,
                c_writeoptions,
                k.as_ptr() as *mut c_char,
                k.len() as size_t,
                v.as_ptr() as *mut c_char,
                v.len() as size_t,
                &mut error,
            );
            leveldb_writeoptions_destroy(c_writeoptions);

            if error.is_null() {
                Ok(())
            } else {
                Err(Error::new_from_char(error))
            }
        }
    }

    /// delete a value from the LevelDB instance.
    ///
    /// The passed key will be compared using the comparator.
    ///
    /// The LevelDB instance will be synced to disk if `options.sync == true`. This is
    /// NOT the default.
    fn delete(&self, options: WriteOptions, key: Slice) -> Result<(), Error> {
        unsafe {
            let k = key.as_bytes();
            let mut error = ptr::null_mut();
            let c_writeoptions = c_writeoptions(options);
            leveldb_delete(
                self.database.ptr,
                c_writeoptions,
                k.as_ptr() as *mut c_char,
                k.len() as size_t,
                &mut error,
            );
            leveldb_writeoptions_destroy(c_writeoptions);
            if error.is_null() {
                Ok(())
            } else {
                Err(Error::new_from_char(error))
            }
        }
    }

    fn get<'a>(
        &self,
        options: ReadOptions<'a>,
        key: Slice,
    ) -> Result<Option<Slice<'static>>, Error> {
        unsafe {
            let k = key.as_bytes();
            let mut error = ptr::null_mut();
            let mut length: size_t = 0;
            let c_readoptions = c_readoptions(&options);
            let result = leveldb_get(
                self.database.ptr,
                c_readoptions,
                k.as_ptr() as *mut c_char,
                k.len() as size_t,
                &mut length,
                &mut error,
            );
            leveldb_readoptions_destroy(c_readoptions);

            if error.is_null() {
                if result.is_null() {
                    Ok(None)
                } else if let Some(bytes) = Bytes::from_raw(result as *mut u8, length) {
                    Ok(Some(bytes.into()))
                } else {
                    Ok(None)
                }
            } else {
                Err(Error::new_from_char(error))
            }
        }
    }
}
