//! Module providing write batches

use super::Database;
use super::error::Error;
use super::slice::Slice;
use crate::binding::*;
use crate::options::{WriteOptions, c_writeoptions};
use libc::{c_char, c_void, size_t};
use std::ptr;
use std::slice;

#[allow(missing_docs)]
pub struct Writebatch {
    ptr: *mut leveldb_writebatch_t,
}

impl Drop for Writebatch {
    fn drop(&mut self) {
        unsafe {
            leveldb_writebatch_destroy(self.ptr);
        }
    }
}

/// Batch access to the LevelDB instance
pub trait Batch {
    /// Write a batch to the database, ensuring success for all items or an error
    fn write(&self, options: WriteOptions, batch: &Writebatch) -> Result<(), Error>;
}

impl Batch for Database {
    fn write(&self, options: WriteOptions, batch: &Writebatch) -> Result<(), Error> {
        unsafe {
            let mut error = ptr::null_mut();
            let c_writeoptions = c_writeoptions(options);

            leveldb_write(self.database.ptr, c_writeoptions, batch.ptr, &mut error);
            leveldb_writeoptions_destroy(c_writeoptions);

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

impl Default for Writebatch {
    fn default() -> Self {
        Self::new()
    }
}

impl Writebatch {
    /// Create a new writebatch
    pub fn new() -> Writebatch {
        let ptr = unsafe { leveldb_writebatch_create() };
        Writebatch { ptr }
    }

    /// Clear the writebatch
    pub fn clear(&mut self) {
        unsafe { leveldb_writebatch_clear(self.ptr) };
    }

    /// Batch a put operation
    pub fn put(&mut self, key: Slice, value: Slice) {
        unsafe {
            let key_bytes = key.as_bytes();
            let value_bytes = value.as_bytes();

            leveldb_writebatch_put(
                self.ptr,
                (key_bytes.as_ptr()) as *mut c_char,
                (key_bytes.len()) as size_t,
                value_bytes.as_ptr() as *mut c_char,
                value_bytes.len() as size_t,
            );
        }
    }

    /// Batch a delete operation
    pub fn delete(&mut self, key: Slice) {
        unsafe {
            let key_bytes = key.as_bytes();

            leveldb_writebatch_delete(
                self.ptr,
                key_bytes.as_ptr() as *mut c_char,
                key_bytes.len() as size_t,
            );
        }
    }

    /// Iterate over the writebatch, returning the resulting iterator
    pub fn iterate<T: WritebatchIterator>(&mut self, iterator: Box<T>) -> Box<T> {
        unsafe {
            let iter = Box::into_raw(iterator);
            leveldb_writebatch_iterate(
                self.ptr,
                iter as *mut c_void,
                put_callback::<T>,
                deleted_callback::<T>,
            );
            Box::from_raw(iter)
        }
    }
}

/// A trait for iterators to iterate over written batches and check their validity.
pub trait WritebatchIterator {
    /// Callback for put items
    fn put(&mut self, key: Slice, value: Slice);

    /// Callback for deleted items
    fn deleted(&mut self, key: Slice);
}

extern "C" fn put_callback<T: WritebatchIterator>(
    state: *mut c_void,
    key: *const c_char,
    keylen: size_t,
    val: *const c_char,
    vallen: size_t,
) {
    unsafe {
        let iter: &mut T = &mut *(state as *mut T);
        let key_slice = slice::from_raw_parts::<u8>(key as *const u8, keylen);
        let val_slice = slice::from_raw_parts::<u8>(val as *const u8, vallen);
        iter.put(Slice::from(key_slice), Slice::from(val_slice));
    }
}

extern "C" fn deleted_callback<T: WritebatchIterator>(
    state: *mut c_void,
    key: *const c_char,
    keylen: size_t,
) {
    unsafe {
        let iter: &mut T = &mut *(state as *mut T);
        let key_slice = slice::from_raw_parts::<u8>(key as *const u8, keylen);
        iter.deleted(Slice::from(key_slice));
    }
}
