use binrw::BinRead;

/// On-disk size of the [`BinaryHeader`] in bytes.
pub const BINARY_HEADER_SIZE: usize = 48;

/// On-disk size of the [`ScdHeader`] in bytes.
pub const SCD_HEADER_SIZE: usize = 32;

/// Expected `type` magic in the [`BinaryHeader`]: `"SEDB"`.
pub const SCD_MAGIC_TYPE: [u8; 4] = *b"SEDB";

/// Expected `sub_type` magic in the [`BinaryHeader`]: `"SSCF"`.
pub const SCD_MAGIC_SUBTYPE: [u8; 4] = *b"SSCF";

/// Value of [`BinaryHeader::endian_type`] for little-endian files (the only supported layout).
pub const ENDIAN_TYPE_LITTLE: u8 = 0;

/// Top-level binary container header at offset 0 of every SCD file.
///
/// The magic bytes `SEDB` / `SSCF` identify the file type; only little-endian files
/// (i.e. `endian_type` equal to [`ENDIAN_TYPE_LITTLE`]) are supported.
///
/// Obtained from [`Scd::binary_header`](crate::scd::parser::Scd::binary_header).
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BinaryHeader {
    /// File type magic, must equal [`SCD_MAGIC_TYPE`] (`"SEDB"`).
    pub r#type: [u8; 4],
    /// Sub-type magic, must equal [`SCD_MAGIC_SUBTYPE`] (`"SSCF"`).
    pub sub_type: [u8; 4],
    /// Format version number.
    pub version: u32,
    /// Endianness flag; `0` = little-endian.
    pub endian_type: u8,
    /// Alignment in bits for data sections.
    pub alignment_bits: u8,
    /// Byte offset from the start of the file to the [`ScdHeader`].
    pub scd_header_offset: u16,
    /// Total declared byte size of the file.
    pub size: u64,
    /// File creation or modification timestamp.
    pub date_time: u64,
    /// Reserved; should be zero.
    pub reserved: [u32; 4],
}

/// Sound-container sub-header located at [`BinaryHeader::scd_header_offset`].
///
/// Holds counts and offset tables for sounds, tracks, and audio entries.
///
/// Obtained from [`Scd::header`](crate::scd::parser::Scd::header).
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScdHeader {
    /// Number of sound entries.
    pub sound_count: u16,
    /// Number of track entries.
    pub track_count: u16,
    /// Number of audio entries (each becomes a [`Track`](crate::scd::track::Track)).
    pub audio_count: u16,
    /// File serial number.
    pub number: u16,
    /// Byte offset to the track offset table.
    pub track_offset: u32,
    /// Byte offset to the audio offset table.
    pub audio_offset: u32,
    /// Byte offset to the layout section.
    pub layout_offset: u32,
    /// Byte offset to the routing section.
    pub routing_offset: u32,
    /// Byte offset to the attribute section.
    pub attribute_offset: u32,
    /// Byte count of end-of-file padding.
    pub eof_padding_size: u16,
    /// Reserved; should be zero.
    pub reserved: u16,
}

#[derive(Debug, BinRead)]
#[brw(little)]
pub(crate) struct WireBinaryHeader {
    pub r#type: [u8; 4],
    pub sub_type: [u8; 4],
    pub version: u32,
    pub endian_type: u8,
    pub alignment_bits: u8,
    pub scd_header_offset: u16,
    pub size: u64,
    pub date_time: u64,
    pub reserved: [u32; 4],
}

impl From<WireBinaryHeader> for BinaryHeader {
    fn from(w: WireBinaryHeader) -> Self {
        BinaryHeader {
            r#type: w.r#type,
            sub_type: w.sub_type,
            version: w.version,
            endian_type: w.endian_type,
            alignment_bits: w.alignment_bits,
            scd_header_offset: w.scd_header_offset,
            size: w.size,
            date_time: w.date_time,
            reserved: w.reserved,
        }
    }
}

#[derive(Debug, BinRead)]
#[brw(little)]
pub(crate) struct WireScdHeader {
    pub sound_count: u16,
    pub track_count: u16,
    pub audio_count: u16,
    pub number: u16,
    pub track_offset: u32,
    pub audio_offset: u32,
    pub layout_offset: u32,
    pub routing_offset: u32,
    pub attribute_offset: u32,
    pub eof_padding_size: u16,
    pub reserved: u16,
}

impl From<WireScdHeader> for ScdHeader {
    fn from(w: WireScdHeader) -> Self {
        ScdHeader {
            sound_count: w.sound_count,
            track_count: w.track_count,
            audio_count: w.audio_count,
            number: w.number,
            track_offset: w.track_offset,
            audio_offset: w.audio_offset,
            layout_offset: w.layout_offset,
            routing_offset: w.routing_offset,
            attribute_offset: w.attribute_offset,
            eof_padding_size: w.eof_padding_size,
            reserved: w.reserved,
        }
    }
}

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

    #[test]
    fn fixed_sizes_match_wire_format() {
        assert_eq!(BINARY_HEADER_SIZE, 48);
        assert_eq!(SCD_HEADER_SIZE, 32);
    }

    #[test]
    fn magic_constants_are_ascii() {
        assert_eq!(&SCD_MAGIC_TYPE, b"SEDB");
        assert_eq!(&SCD_MAGIC_SUBTYPE, b"SSCF");
    }
}
