//! CVSS metrics.

use crate::{Error, Result};
use alloc::borrow::ToOwned;
use core::{
    fmt::{self, Debug, Display},
    str::FromStr,
};

/// Trait for CVSS metrics.
pub trait Metric: Copy + Clone + Debug + Display + Eq + FromStr + Ord {
    /// [`MetricType`] of this metric.
    const TYPE: MetricType;

    /// Get the name of this metric.
    fn name() -> &'static str {
        Self::TYPE.name()
    }

    /// Get CVSS v3.1 score for this metric.
    fn score(self) -> f64;

    /// Get `str` describing this metric's value
    fn as_str(self) -> &'static str;
}

/// Enum over all of the available metrics.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MetricType {
    /// Availability Impact (A)
    A,

    /// Attack Complexity (AC)
    AC,

    /// Attack Vector (AV)
    AV,

    /// Confidentiality Impact (C)
    C,

    /// Integrity Impact (I)
    I,

    /// Privileges Required (PR)
    PR,

    /// Scope (S)
    S,

    /// User Interaction (UI)
    UI,
}

impl MetricType {
    /// Get the name of this metric (i.e. acronym)
    pub fn name(self) -> &'static str {
        match self {
            Self::A => "A",
            Self::AC => "AC",
            Self::AV => "AV",
            Self::C => "C",
            Self::I => "I",
            Self::PR => "PR",
            Self::S => "S",
            Self::UI => "UI",
        }
    }

    /// Get a description of this metric.
    pub fn description(self) -> &'static str {
        match self {
            Self::A => "Availability Impact",
            Self::AC => "Attack Complexity",
            Self::AV => "Attack Vector",
            Self::C => "Confidentiality Impact",
            Self::I => "Integrity Impact",
            Self::PR => "Privileges Required",
            Self::S => "Scope",
            Self::UI => "User Interaction",
        }
    }
}

impl Display for MetricType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.name())
    }
}

impl FromStr for MetricType {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s {
            "A" => Ok(Self::A),
            "AC" => Ok(Self::AC),
            "AV" => Ok(Self::AV),
            "C" => Ok(Self::C),
            "I" => Ok(Self::I),
            "PR" => Ok(Self::PR),
            "S" => Ok(Self::S),
            "UI" => Ok(Self::UI),
            _ => Err(Error::UnknownMetric { name: s.to_owned() }),
        }
    }
}
