use std::fmt;
use std::marker;

/// A trait that all HTTP/2 frame header flags need to implement.
pub trait Flag: fmt::Debug + Copy + Clone + Sized {
    /// Returns a bit mask that represents the flag.
    fn bitmask(&self) -> u8;

    fn flags() -> &'static [Self];

    fn to_flags(&self) -> Flags<Self> {
        Flags::new(self.bitmask())
    }
}

/// A helper struct that can be used by all frames that do not define any flags.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NoFlag {}

impl Flag for NoFlag {
    fn bitmask(&self) -> u8 {
        match *self {}
    }

    fn flags() -> &'static [Self] {
        static FLAGS: &'static [NoFlag] = &[];
        FLAGS
    }
}

#[derive(PartialEq, Eq, Copy, Clone)]
/// 4.1
/// Flags: An 8-bit field reserved for boolean flags specific to the frame type.
///
/// Flags are assigned semantics specific to the indicated frame type.
/// Flags that have no defined semantics for a particular frame type
/// MUST be ignored and MUST be left unset (0x0) when sending.
pub struct Flags<F: Flag + 'static>(pub u8, marker::PhantomData<F>);

impl<F: Flag> Flags<F> {
    /// Create new flags object with given flags bitset.
    pub fn new(value: u8) -> Flags<F> {
        Flags(value, marker::PhantomData)
    }

    /// Is flag set?
    pub fn is_set(&self, flag: F) -> bool {
        (self.0 & flag.bitmask()) != 0
    }

    /// Set flag.
    pub fn set(&mut self, flag: F) {
        self.0 |= flag.bitmask()
    }

    /// Unset flag.
    pub fn clear(&mut self, flag: F) {
        self.0 &= !flag.bitmask();
    }

    /// Set flag.
    pub fn with(&self, flag: F) -> Flags<F> {
        let mut flags = self.clone();
        flags.set(flag);
        flags
    }

    /// Unset flag.
    pub fn without(&self, flag: F) -> Flags<F> {
        let mut flags = self.clone();
        flags.clear(flag);
        flags
    }
}

impl<F: Flag> Default for Flags<F> {
    fn default() -> Self {
        Flags::new(0)
    }
}

impl<F: Flag> fmt::Debug for Flags<F> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.0 == 0 {
            return write!(f, "0");
        }

        let mut copy: Flags<F> = (*self).clone();

        let mut first = true;
        for &flag in F::flags() {
            if copy.is_set(flag) {
                if !first {
                    write!(f, "|")?;
                }
                first = false;

                write!(f, "{:?}", flag)?;
                copy.clear(flag);
            }
        }

        // unknown flags
        if copy.0 != 0 {
            if !first {
                write!(f, "|")?;
            }
            write!(f, "0x{:x}", copy.0)?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    enum FakeFlag {
        Foo = 0x1,
        Bar = 0x80,
    }

    impl Flag for FakeFlag {
        fn bitmask(&self) -> u8 {
            *self as u8
        }

        fn flags() -> &'static [Self] {
            static FLAGS: &'static [FakeFlag] = &[FakeFlag::Foo, FakeFlag::Bar];
            FLAGS
        }
    }

    #[test]
    fn fmt_flags() {
        assert_eq!("0", format!("{:?}", Flags::<FakeFlag>::new(0)));
        assert_eq!("Foo", format!("{:?}", Flags::<FakeFlag>::new(0x1)));
        assert_eq!("0x62", format!("{:?}", Flags::<FakeFlag>::new(0x62)));
        assert_eq!("Foo|Bar", format!("{:?}", Flags::<FakeFlag>::new(0x81)));
        assert_eq!(
            "Foo|Bar|0x62",
            format!("{:?}", Flags::<FakeFlag>::new(0x81 | 0x62))
        );
    }
}
