use std::fmt;

#[cfg(test)]
use quickcheck::{Arbitrary, Gen};

use crate::types::Bitfield;

/// Describes preferences regarding key servers.
///
/// Key server preferences are specified in [Section 5.2.3.25 of RFC 9580].
///
/// [Section 5.2.3.25 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25
///
/// The keyserver preferences are set by the user's OpenPGP
/// implementation to communicate them to any peers.
///
/// # A note on equality
///
/// `PartialEq` compares the serialized form of the two key server
/// preference sets.  If you prefer to compare two key server
/// preference sets for semantic equality, you should use
/// [`KeyServerPreferences::normalized_eq`].  The difference between
/// semantic equality and serialized equality is that semantic
/// equality ignores differences in the amount of padding.
///
///   [`KeyServerPreferences::normalized_eq`]: KeyServerPreferences::normalized_eq()
///
/// # Examples
///
/// ```
/// use sequoia_openpgp as openpgp;
/// # use openpgp::Result;
/// use openpgp::cert::prelude::*;
/// use openpgp::policy::StandardPolicy;
///
/// # fn main() -> Result<()> {
/// let p = &StandardPolicy::new();
///
/// let (cert, _) =
///     CertBuilder::general_purpose(Some("alice@example.org"))
///     .generate()?;
///
/// match cert.with_policy(p, None)?.primary_userid()?.key_server_preferences() {
///     Some(preferences) => {
///         println!("Certificate holder's keyserver preferences:");
///         assert!(preferences.no_modify());
/// #       unreachable!();
///     }
///     None => {
///         println!("Certificate Holder did not specify any key server preferences.");
///     }
/// }
/// # Ok(()) }
/// ```
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyServerPreferences(Bitfield);
assert_send_and_sync!(KeyServerPreferences);

impl fmt::Debug for KeyServerPreferences {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut need_comma = false;
        if self.no_modify() {
            f.write_str("no modify")?;
            need_comma = true;
        }

        for i in self.0.iter_set() {
            match i {
                KEYSERVER_PREFERENCE_NO_MODIFY => (),
                i => {
                    if need_comma { f.write_str(", ")?; }
                    write!(f, "#{}", i)?;
                    need_comma = true;
                },
            }
        }

        // Mention any padding, as equality is sensitive to this.
        if let Some(padding) = self.0.padding_bytes() {
            if need_comma { f.write_str(", ")?; }
            write!(f, "+padding({} bytes)", padding)?;
        }

        Ok(())
    }
}

impl KeyServerPreferences {
    /// Creates a new instance from `bits`.
    pub fn new<B: AsRef<[u8]>>(bits: B) -> Self {
        KeyServerPreferences(bits.as_ref().to_vec().into())
    }

    /// Returns an empty key server preference set.
    pub fn empty() -> Self {
        Self::new(&[])
    }

    /// Returns a reference to the underlying [`Bitfield`].
    pub fn as_bitfield(&self) -> &Bitfield {
        &self.0
    }

    /// Returns a mutable reference to the underlying [`Bitfield`].
    pub fn as_bitfield_mut(&mut self) -> &mut Bitfield {
        &mut self.0
    }

    /// Compares two key server preference sets for semantic equality.
    ///
    /// `KeyServerPreferences` implementation of `PartialEq` compares
    /// two key server preference sets for serialized equality.  That
    /// is, the `PartialEq` implementation considers two key server
    /// preference sets to *not* be equal if they have different
    /// amounts of padding.  This comparison function ignores padding.
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let a = KeyServerPreferences::new(&[0x1]);
    /// let b = KeyServerPreferences::new(&[0x1, 0x0]);
    ///
    /// assert!(a != b);
    /// assert!(a.normalized_eq(&b));
    /// # Ok(()) }
    /// ```
    pub fn normalized_eq(&self, other: &Self) -> bool {
        self.0.normalized_eq(&other.0)
    }

    /// Returns whether the specified keyserver preference flag is set.
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// // Keyserver Preferences flags 0 and 2.
    /// let ksp = KeyServerPreferences::new(&[0x5]);
    ///
    /// assert!(ksp.get(0));
    /// assert!(! ksp.get(1));
    /// assert!(ksp.get(2));
    /// assert!(! ksp.get(3));
    /// assert!(! ksp.get(8));
    /// assert!(! ksp.get(80));
    /// # assert!(! ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn get(&self, bit: usize) -> bool {
        self.0.get(bit)
    }

    /// Sets the specified keyserver preference flag.
    ///
    /// This also clears any padding (trailing NUL bytes).
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let ksp = KeyServerPreferences::empty().set(0).set(2);
    ///
    /// assert!(ksp.get(0));
    /// assert!(! ksp.get(1));
    /// assert!(ksp.get(2));
    /// assert!(! ksp.get(3));
    /// # assert!(! ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn set(mut self, bit: usize) -> Self {
        self.0.set(bit);
        self.0.canonicalize();
        self
    }

    /// Clears the specified keyserver preference flag.
    ///
    /// This also clears any padding (trailing NUL bytes).
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let ksp = KeyServerPreferences::empty().set(0).set(2).clear(2);
    ///
    /// assert!(ksp.get(0));
    /// assert!(! ksp.get(1));
    /// assert!(! ksp.get(2));
    /// assert!(! ksp.get(3));
    /// # assert!(! ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn clear(mut self, bit: usize) -> Self {
        self.0.clear(bit);
        self.0.canonicalize();
        self
    }

    /// Returns whether the certificate's owner requests that the
    /// certificate is not modified.
    ///
    /// If this flag is set, the certificate's owner requests that the
    /// certificate should only be changed by the owner and the key
    /// server's operator.
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let ksp = KeyServerPreferences::empty();
    /// assert!(! ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn no_modify(&self) -> bool {
        self.get(KEYSERVER_PREFERENCE_NO_MODIFY)
    }

    /// Requests that the certificate is not modified.
    ///
    /// See [`KeyServerPreferences::no_modify`].
    ///
    ///   [`KeyServerPreferences::no_modify`]: KeyServerPreferences::no_modify()
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let ksp = KeyServerPreferences::empty().set_no_modify();
    /// assert!(ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn set_no_modify(self) -> Self {
        self.set(KEYSERVER_PREFERENCE_NO_MODIFY)
    }

    /// Clears the request that the certificate is not modified.
    ///
    /// See [`KeyServerPreferences::no_modify`].
    ///
    ///   [`KeyServerPreferences::no_modify`]: KeyServerPreferences::no_modify()
    ///
    /// # Examples
    ///
    /// ```
    /// use sequoia_openpgp as openpgp;
    /// use openpgp::types::KeyServerPreferences;
    ///
    /// # fn main() -> openpgp::Result<()> {
    /// let ksp = KeyServerPreferences::new(&[0x80][..]);
    /// assert!(ksp.no_modify());
    /// let ksp = ksp.clear_no_modify();
    /// assert!(! ksp.no_modify());
    /// # Ok(()) }
    /// ```
    pub fn clear_no_modify(self) -> Self {
        self.clear(KEYSERVER_PREFERENCE_NO_MODIFY)
    }
}

/// The key holder requests that this key only be modified or updated
/// by the key holder or an administrator of the key server.
const KEYSERVER_PREFERENCE_NO_MODIFY: usize = 7;

#[cfg(test)]
impl Arbitrary for KeyServerPreferences {
    fn arbitrary(g: &mut Gen) -> Self {
        Self::new(Vec::arbitrary(g))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn basics() -> crate::Result<()> {
        let p = KeyServerPreferences::empty();
        assert_eq!(p.no_modify(), false);
        let p = KeyServerPreferences::new(&[]);
        assert_eq!(p.no_modify(), false);
        let p = KeyServerPreferences::new(&[0xff]);
        assert_eq!(p.no_modify(), true);
        Ok(())
    }

    quickcheck! {
        fn roundtrip(val: KeyServerPreferences) -> bool {
            let mut q_bytes = val.as_bitfield().as_bytes().to_vec();
            let q = KeyServerPreferences::new(&q_bytes);
            assert_eq!(val, q);
            assert!(val.normalized_eq(&q));

            // Add some padding to q.  Make sure they are still equal.
            q_bytes.push(0);
            let q = KeyServerPreferences::new(&q_bytes);
            assert!(val != q);
            assert!(val.normalized_eq(&q));

            q_bytes.push(0);
            let q = KeyServerPreferences::new(&q_bytes);
            assert!(val != q);
            assert!(val.normalized_eq(&q));

            true
        }
    }
}
