//! Filter policy implementations for LevelDB.
//!
//! Filter policies are used to reduce the number of disk reads during
//! LevelDB queries by quickly eliminating keys that cannot be in a
//! particular data block.

use crate::binding::{
    leveldb_filterpolicy_create, leveldb_filterpolicy_create_bloom, leveldb_filterpolicy_destroy,
    leveldb_filterpolicy_t,
};
use libc::{c_char, c_uchar, c_void};
use std::ffi::CString;
use std::os::raw::c_int;
use std::slice;

/// Common trait for all filter policy implementations
pub trait LevelDBFilterPolicy {
    /// Get the raw pointer to the LevelDB filter policy
    fn raw_ptr(&self) -> *mut leveldb_filterpolicy_t;
}

/// BloomFilterPolicy wraps LevelDB's built-in bloom filter policy
pub struct BloomFilterPolicy {
    ptr: *mut leveldb_filterpolicy_t,
}

impl BloomFilterPolicy {
    /// Create a new BloomFilterPolicy with the given bits per key
    pub fn new(bits_per_key: i32) -> BloomFilterPolicy {
        let ptr = unsafe { leveldb_filterpolicy_create_bloom(bits_per_key) };
        BloomFilterPolicy { ptr }
    }
}

impl LevelDBFilterPolicy for BloomFilterPolicy {
    /// Get the raw pointer to the LevelDB filter policy
    fn raw_ptr(&self) -> *mut leveldb_filterpolicy_t {
        self.ptr
    }
}

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

/// Trait for custom filter policies that can be used with LevelDB
pub trait FilterPolicy {
    /// Create a filter for the given keys
    fn create_filter(&self, keys: &[&[u8]]) -> Vec<u8>;

    /// Check if a key may be present in the filter
    fn key_may_match(&self, key: &[u8], filter: &[u8]) -> bool;

    /// Get the name of this filter policy
    fn name(&self) -> &'static str;
}

/// Wrapper for custom filter policies that implements the LevelDB FFI interface
pub struct CustomFilterPolicy<T: FilterPolicy> {
    ptr: *mut leveldb_filterpolicy_t,
    policy: T,
}

impl<T: FilterPolicy> CustomFilterPolicy<T> {
    /// Create a new custom filter policy
    pub fn new(policy: T) -> Self {
        let boxed_policy = Box::new(policy);
        let state = Box::into_raw(boxed_policy) as *mut c_void;

        extern "C" fn destructor<T: FilterPolicy>(state: *mut c_void) {
            unsafe {
                let _ = Box::from_raw(state as *mut T);
            }
        }

        extern "C" fn create_filter<T: FilterPolicy>(
            state: *mut c_void,
            key_array: *const *const c_char,
            key_length_array: *const usize,
            num_keys: c_int,
            filter_length: *mut usize,
        ) -> *mut c_char {
            unsafe {
                let policy = &*(state as *const T);
                let keys: Vec<&[u8]> = (0..num_keys as usize)
                    .map(|i| {
                        let key_ptr = *key_array.add(i);
                        let key_len = *key_length_array.add(i);
                        slice::from_raw_parts(key_ptr as *const u8, key_len)
                    })
                    .collect();

                let filter_data = policy.create_filter(&keys);
                *filter_length = filter_data.len();

                let mut boxed_filter = filter_data.into_boxed_slice();
                let ptr = boxed_filter.as_mut_ptr() as *mut c_char;
                let _ = Box::into_raw(boxed_filter);
                ptr
            }
        }

        extern "C" fn key_may_match<T: FilterPolicy>(
            state: *mut c_void,
            key: *const c_char,
            key_length: usize,
            filter: *const c_char,
            filter_length: usize,
        ) -> c_uchar {
            unsafe {
                let policy = &*(state as *const T);
                let key_slice = slice::from_raw_parts(key as *const u8, key_length);
                let filter_slice = slice::from_raw_parts(filter as *const u8, filter_length);

                policy.key_may_match(key_slice, filter_slice) as c_uchar
            }
        }

        extern "C" fn name<T: FilterPolicy>(state: *mut c_void) -> *const c_char {
            unsafe {
                let policy = &*(state as *const T);
                let name_str = policy.name();
                let cstring = CString::new(name_str).unwrap();
                cstring.into_raw()
            }
        }

        let ptr = unsafe {
            leveldb_filterpolicy_create(
                state,
                destructor::<T>,
                create_filter::<T>,
                key_may_match::<T>,
                name::<T>,
            )
        };

        let policy = unsafe { *Box::from_raw(state as *mut T) };

        CustomFilterPolicy { ptr, policy }
    }

    /// Get a reference to the underlying filter policy
    pub fn policy(&self) -> &T {
        &self.policy
    }
}

impl<T: FilterPolicy> LevelDBFilterPolicy for CustomFilterPolicy<T> {
    /// Get the raw pointer to the LevelDB filter policy
    fn raw_ptr(&self) -> *mut leveldb_filterpolicy_t {
        self.ptr
    }
}

impl<T: FilterPolicy> Drop for CustomFilterPolicy<T> {
    fn drop(&mut self) {
        unsafe {
            leveldb_filterpolicy_destroy(self.ptr);
        }
    }
}
