/// Audio codec discriminant stored in each track's [`AudioBasicDesc`].
///
/// [`AudioBasicDesc`]: crate::scd::track::AudioBasicDesc
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScdFormat {
    /// Track slot exists but contains no audio data (raw discriminant `-1`).
    Empty,
    /// OGG Vorbis, optionally XOR-obfuscated (raw discriminant `6`).
    OggVorbis,
    /// PS3/Xbox MP3 with optional STBL seek table (raw discriminant `7`).
    Mp3,
    /// Microsoft ADPCM with WAVEFORMATEX header (raw discriminant `12`).
    MsAdpcm,
    /// Sony ATRAC9 with superframe header (raw discriminant `22`).
    Atrac9,
    /// An unrecognised codec discriminant.
    Unknown(i32),
}

impl ScdFormat {
    /// Converts the on-disk `i32` codec discriminant to an [`ScdFormat`].
    ///
    /// Unknown discriminants are wrapped in [`ScdFormat::Unknown`].
    #[must_use]
    pub fn from_raw(raw: i32) -> Self {
        match raw {
            -1 => ScdFormat::Empty,
            6 => ScdFormat::OggVorbis,
            7 => ScdFormat::Mp3,
            12 => ScdFormat::MsAdpcm,
            22 => ScdFormat::Atrac9,
            other => ScdFormat::Unknown(other),
        }
    }

    /// Returns the on-disk `i32` discriminant for this format.
    #[must_use]
    pub fn as_raw(self) -> i32 {
        match self {
            ScdFormat::Empty => -1,
            ScdFormat::OggVorbis => 6,
            ScdFormat::Mp3 => 7,
            ScdFormat::MsAdpcm => 12,
            ScdFormat::Atrac9 => 22,
            ScdFormat::Unknown(raw) => raw,
        }
    }

    /// Returns `true` for any named codec variant other than [`ScdFormat::Empty`].
    #[must_use]
    pub fn is_known_codec(self) -> bool {
        !matches!(self, ScdFormat::Empty | ScdFormat::Unknown(_))
    }
}

impl std::fmt::Display for ScdFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ScdFormat::Empty => f.write_str("Empty"),
            ScdFormat::OggVorbis => f.write_str("OggVorbis"),
            ScdFormat::Mp3 => f.write_str("Mp3"),
            ScdFormat::MsAdpcm => f.write_str("MsAdpcm"),
            ScdFormat::Atrac9 => f.write_str("Atrac9"),
            ScdFormat::Unknown(raw) => write!(f, "Unknown({raw})"),
        }
    }
}

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

    #[test]
    fn from_raw_round_trips_every_known_discriminant() {
        let cases: &[(i32, ScdFormat)] = &[
            (-1, ScdFormat::Empty),
            (6, ScdFormat::OggVorbis),
            (7, ScdFormat::Mp3),
            (12, ScdFormat::MsAdpcm),
            (22, ScdFormat::Atrac9),
        ];
        for &(raw, expected) in cases {
            assert_eq!(ScdFormat::from_raw(raw), expected, "raw {raw}");
            assert_eq!(expected.as_raw(), raw, "as_raw {expected:?}");
        }
    }

    #[test]
    fn unknown_round_trips_verbatim() {
        let raw = 0x1234;
        let fmt = ScdFormat::from_raw(raw);
        assert_eq!(fmt, ScdFormat::Unknown(raw));
        assert_eq!(fmt.as_raw(), raw);

        let neg = -42;
        let fmt = ScdFormat::from_raw(neg);
        assert_eq!(fmt, ScdFormat::Unknown(neg));
        assert_eq!(fmt.as_raw(), neg);
    }

    #[test]
    fn is_known_codec_covers_named_variants() {
        assert!(ScdFormat::OggVorbis.is_known_codec());
        assert!(ScdFormat::Mp3.is_known_codec());
        assert!(ScdFormat::MsAdpcm.is_known_codec());
        assert!(ScdFormat::Atrac9.is_known_codec());
        assert!(!ScdFormat::Empty.is_known_codec());
        assert!(!ScdFormat::Unknown(0).is_known_codec());
    }
}
