use crate::bls::BlsPubkey;
use crate::error::{Error, check};
use crate::ffi;

/// `AnchorSet` is a collection of trusted BLS anchor public keys with a signature threshold.
///
/// `AnchorSet` is `Send + Sync` and may be shared across threads. The threshold is set at
/// construction time and governs how many anchor signatures are required for
/// [`Checkpoint::verify`](crate::checkpoint::Checkpoint::verify) to succeed.
pub struct AnchorSet {
    ptr: *mut ffi::nwep_anchor_set,
}

unsafe impl Send for AnchorSet {}
unsafe impl Sync for AnchorSet {}

impl AnchorSet {
    /// `new` creates an empty `AnchorSet` with the given signature threshold.
    ///
    /// `threshold` is the minimum number of anchor signatures required to validate a
    /// checkpoint. A threshold of 0 means no signatures are required (not recommended).
    ///
    /// # Errors
    ///
    /// Returns `Err` if the underlying C allocation fails.
    pub fn new(threshold: usize) -> Result<Self, Error> {
        let mut ptr: *mut ffi::nwep_anchor_set = std::ptr::null_mut();
        check(unsafe { ffi::nwep_anchor_set_new(&mut ptr, threshold) })?;
        Ok(AnchorSet { ptr })
    }

    /// `add` adds a BLS public key to the anchor set.
    ///
    /// `builtin` should be `true` for keys that are part of the initial hardcoded
    /// trust configuration, and `false` for dynamically added anchors. The distinction
    /// is used by the C library when persisting the anchor set.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `pk` is already present or if the C call fails.
    pub fn add(&mut self, pk: &BlsPubkey, builtin: bool) -> Result<(), Error> {
        let ffi_pk = ffi::nwep_bls_pubkey { data: pk.0 };
        check(unsafe { ffi::nwep_anchor_set_add(self.ptr, &ffi_pk, builtin as i32) })
    }

    /// `remove` removes a BLS public key from the anchor set.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `pk` is not present or if the C call fails.
    pub fn remove(&mut self, pk: &BlsPubkey) -> Result<(), Error> {
        let ffi_pk = ffi::nwep_bls_pubkey { data: pk.0 };
        check(unsafe { ffi::nwep_anchor_set_remove(self.ptr, &ffi_pk) })
    }

    /// `size` returns the number of anchor keys currently in the set.
    pub fn size(&self) -> usize {
        unsafe { ffi::nwep_anchor_set_size(self.ptr) }
    }

    /// `threshold` returns the minimum number of signatures required to validate a checkpoint.
    pub fn threshold(&self) -> usize {
        unsafe { ffi::nwep_anchor_set_threshold(self.ptr) }
    }

    /// `get` returns the BLS public key and `builtin` flag at position `idx`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `idx >= size()`.
    pub fn get(&self, idx: usize) -> Result<(BlsPubkey, bool), Error> {
        let mut pk = ffi::nwep_bls_pubkey { data: [0u8; 48] };
        let mut builtin = 0i32;
        check(unsafe { ffi::nwep_anchor_set_get(self.ptr, idx, &mut pk, &mut builtin) })?;
        Ok((BlsPubkey(pk.data), builtin != 0))
    }

    /// `contains` returns `true` if the given BLS public key is in the anchor set.
    pub fn contains(&self, pk: &BlsPubkey) -> bool {
        let ffi_pk = ffi::nwep_bls_pubkey { data: pk.0 };
        unsafe { ffi::nwep_anchor_set_contains(self.ptr, &ffi_pk) != 0 }
    }

    /// `iter` returns an iterator over all `(BlsPubkey, builtin)` pairs in the set.
    pub fn iter(&self) -> impl Iterator<Item = (BlsPubkey, bool)> + '_ {
        (0..self.size()).filter_map(|i| self.get(i).ok())
    }

    pub(crate) fn as_ptr(&self) -> *const ffi::nwep_anchor_set {
        self.ptr
    }

    pub(crate) fn as_mut_ptr(&mut self) -> *mut ffi::nwep_anchor_set {
        self.ptr
    }
}

impl Drop for AnchorSet {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { ffi::nwep_anchor_set_free(self.ptr) }
        }
    }
}
