//! Vulnerability encoding for par calculation

use thiserror::Error;

use core::fmt;
use core::str::FromStr;

bitflags::bitflags! {
    /// Vulnerability of pairs
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
    pub struct Vulnerability: u8 {
        /// North-South are vulnerable
        const NS = 1;
        /// East-West are vulnerable
        const EW = 2;
    }
}

impl Vulnerability {
    /// Both sides vulnerable
    pub const ALL: Self = Self::all();

    /// Neither side vulnerable
    pub const NONE: Self = Self::empty();

    /// Convert to encoding in [`ddss_sys`]
    #[must_use]
    #[inline]
    pub const fn to_sys(self) -> i32 {
        const ALL: u8 = Vulnerability::all().bits();
        const NS: u8 = Vulnerability::NS.bits();
        const EW: u8 = Vulnerability::EW.bits();

        match self.bits() {
            0 => 0,
            ALL => 1,
            NS => 2,
            EW => 3,
            _ => unreachable!(),
        }
    }

    /// Conditionally swap [`NS`](Self::NS) and [`EW`](Self::EW)
    #[must_use]
    #[inline]
    pub const fn rotate(self, condition: bool) -> Self {
        // The trick: multiplying by 0x55 duplicates bit 0 into bit 1 (and bit 1
        // into bit 2, etc.).  Shifting right by 1 when `condition` is true
        // effectively moves bit 0 → nowhere and bit 1 → bit 0, swapping NS/EW.
        Self::from_bits_truncate((self.bits() * 0x55) >> (condition as u8))
    }
}

/// Error returned when parsing a [`Vulnerability`] fails
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
#[error("Invalid vulnerability: expected one of none, ns, ew, both")]
pub struct ParseVulnerabilityError;

impl FromStr for Vulnerability {
    type Err = ParseVulnerabilityError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "none" => Ok(Self::NONE),
            "ns" => Ok(Self::NS),
            "ew" => Ok(Self::EW),
            "both" | "all" => Ok(Self::ALL),
            _ => Err(ParseVulnerabilityError),
        }
    }
}

impl fmt::Display for Vulnerability {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            Self::NONE => "none",
            Self::NS => "ns",
            Self::EW => "ew",
            Self::ALL => "both",
            _ => unreachable!(),
        })
    }
}

/// Exhaustively check correctness of [`Vulnerability::rotate`] at compile time
const _: () = {
    const ALL: Vulnerability = Vulnerability::all();
    const NONE: Vulnerability = Vulnerability::empty();

    assert!(matches!(ALL.rotate(true), ALL));
    assert!(matches!(NONE.rotate(true), NONE));
    assert!(matches!(Vulnerability::NS.rotate(true), Vulnerability::EW));
    assert!(matches!(Vulnerability::EW.rotate(true), Vulnerability::NS));

    assert!(matches!(ALL.rotate(false), ALL));
    assert!(matches!(NONE.rotate(false), NONE));
    assert!(matches!(Vulnerability::NS.rotate(false), Vulnerability::NS));
    assert!(matches!(Vulnerability::EW.rotate(false), Vulnerability::EW));
};
