//! The subset of an SCTE 35-1 `splice_info_section()` the opportunity report reads
//! (extensions/opportunity-report.md §5): the CRC, the header, `splice_insert()`,
//! `time_signal()` and `splice_null()`, and the segmentation descriptors of the descriptor loop,
//! for what a section states (§5.2): its id, its duration and its slots. A section is read only as
//! far as this; another command or descriptor is passed over whole.
//!
//! [`write`] builds sections from their fields for the corpus.

/// The MPEG-2 CRC (ISO/IEC 13818-1 Annex A): polynomial 0x04C11DB7, register preset to all ones,
/// no reflection, no final inversion.
pub fn crc32(b: &[u8]) -> u32 {
    let mut c: u32 = 0xFFFF_FFFF;
    for &x in b {
        c ^= (x as u32) << 24;
        for _ in 0..8 {
            c = if c & 0x8000_0000 != 0 { (c << 1) ^ 0x04C1_1DB7 } else { c << 1 };
        }
    }
    c
}

/// The command types this text reads.
pub mod command {
    pub const SPLICE_NULL: u8 = 0x00;
    pub const SPLICE_INSERT: u8 = 0x05;
    pub const TIME_SIGNAL: u8 = 0x06;
}

/// The segmentation types whose start makes an out (cue-injection §6.2).
pub const OPENING: [u8; 5] = [0x22, 0x30, 0x32, 0x34, 0x36];
/// The placement opportunity starts.
pub const PLACEMENT: [u8; 2] = [0x34, 0x36];
/// The types that may carry the sub-segment fields (SCTE 35-1 Table 20).
pub const SUB_SEGMENTED: [u8; 8] = [0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x44, 0x46];

/// A segmentation descriptor, as far as §5.1 reads it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Segmentation {
    pub event_id: u32,
    pub cancelled: bool,
    pub duration: Option<u64>,
    pub type_id: u8,
    /// (`sub_segment_num`, `sub_segments_expected`), when carried.
    pub sub: Option<(u8, u8)>,
}

/// What a section states (§5.2), and what §5.3 holds it to.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct States {
    /// `splice_command_type`, or `None` for an encrypted section.
    pub command: Option<u8>,
    /// A `splice_insert()`: (its cancel indicator, its `out_of_network_indicator`).
    pub insert: Option<(bool, bool)>,
    pub id: Option<u32>,
    pub duration: Option<u64>,
    pub slots: Option<u64>,
}

impl States {
    fn nothing(command: Option<u8>) -> States {
        States { command, insert: None, id: None, duration: None, slots: None }
    }

    /// The command's name, as the outcome states it.
    pub fn command_name(&self) -> &'static str {
        match self.command {
            None => "encrypted",
            Some(command::SPLICE_INSERT) => "splice_insert",
            Some(command::TIME_SIGNAL) => "time_signal",
            Some(command::SPLICE_NULL) => "splice_null",
            Some(_) => "other",
        }
    }
}

/// A big-endian bit cursor over `b[..end]`; running out is a refusal.
struct Bits<'a> {
    b: &'a [u8],
    /// The position, in bits.
    at: usize,
    /// The end, in bytes.
    end: usize,
}

impl Bits<'_> {
    fn take(&mut self, n: usize) -> Result<u64, ()> {
        if self.at + n > self.end * 8 {
            return Err(());
        }
        let mut v: u64 = 0;
        for _ in 0..n {
            let bit = (self.b[self.at / 8] >> (7 - self.at % 8)) & 1;
            v = (v << 1) | bit as u64;
            self.at += 1;
        }
        Ok(v)
    }
    fn skip(&mut self, n_bytes: usize) -> Result<(), ()> {
        if self.at / 8 + n_bytes > self.end {
            return Err(());
        }
        self.at += 8 * n_bytes;
        Ok(())
    }
    fn byte_pos(&self) -> usize {
        self.at / 8
    }
}

/// A `splice_time()`: one byte, or five with `pts_time`.
fn splice_time(r: &mut Bits) -> Result<(), ()> {
    if r.take(1)? == 1 {
        r.take(6)?;
        r.take(33)?;
    } else {
        r.take(7)?;
    }
    Ok(())
}

/// `splice_insert()`: (cancel, out_of_network, splice_event_id, break duration).
fn splice_insert(r: &mut Bits) -> Result<(bool, bool, u32, Option<u64>), ()> {
    let id = r.take(32)? as u32;
    let cancel = r.take(1)? == 1;
    r.take(7)?;
    if cancel {
        return Ok((true, false, id, None));
    }
    let out = r.take(1)? == 1;
    let program = r.take(1)? == 1;
    let duration_flag = r.take(1)? == 1;
    let immediate = r.take(1)? == 1;
    r.take(4)?;
    if program && !immediate {
        splice_time(r)?;
    }
    if !program {
        let n = r.take(8)?;
        for _ in 0..n {
            r.take(8)?;
            if !immediate {
                splice_time(r)?;
            }
        }
    }
    let duration = if duration_flag {
        r.take(1)?;
        r.take(6)?;
        Some(r.take(33)?)
    } else {
        None
    };
    r.take(16)?;
    r.take(8)?;
    r.take(8)?;
    Ok((false, out, id, duration))
}

/// A segmentation descriptor's body after its identifier, filling `[from, to)` exactly.
fn segmentation(b: &[u8], from: usize, to: usize) -> Result<Segmentation, ()> {
    let mut r = Bits { b, at: from * 8, end: to };
    let event_id = r.take(32)? as u32;
    let cancelled = r.take(1)? == 1;
    r.take(7)?;
    if cancelled {
        return if r.byte_pos() == to { Ok(Segmentation { event_id, cancelled, duration: None, type_id: 0, sub: None }) } else { Err(()) };
    }
    let program = r.take(1)? == 1;
    let duration_flag = r.take(1)? == 1;
    r.take(6)?;
    if !program {
        let n = r.take(8)?;
        r.skip(6 * n as usize)?;
    }
    let duration = if duration_flag { Some(r.take(40)?) } else { None };
    r.take(8)?;
    let upid_len = r.take(8)?;
    r.skip(upid_len as usize)?;
    let type_id = r.take(8)? as u8;
    r.take(8)?;
    r.take(8)?;
    let left = to - r.byte_pos();
    let sub = match left {
        0 => None,
        2 if SUB_SEGMENTED.contains(&type_id) => Some((r.take(8)? as u8, r.take(8)? as u8)),
        _ => return Err(()),
    };
    Ok(Segmentation { event_id, cancelled, duration, type_id, sub })
}

/// A section that does not read (§5.1): `cue_section_invalid`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Invalid;

/// Read a section (§5.1) for what it states (§5.2).
pub fn read(s: &[u8]) -> Result<States, Invalid> {
    read_section(s).map_err(|()| Invalid)
}

fn read_section(s: &[u8]) -> Result<States, ()> {
    let n = s.len();
    if n < 20 {
        return Err(());
    }
    let crc_at = n - 4;
    if crc32(&s[..crc_at]) != u32::from_be_bytes(s[crc_at..].try_into().expect("4 bytes")) {
        return Err(());
    }
    if s[3] != 0 {
        return Err(());
    }
    if s[4] >> 7 == 1 {
        return Ok(States::nothing(None));
    }
    let cmd_len = (((s[11] & 0x0F) as usize) << 8) | s[12] as usize;
    let cmd = s[13];
    let readable = matches!(cmd, command::SPLICE_INSERT | command::TIME_SIGNAL | command::SPLICE_NULL);
    if cmd_len == 0xFFF && !readable {
        return Ok(States::nothing(Some(cmd)));
    }
    let end = if cmd_len == 0xFFF { crc_at } else { 14 + cmd_len };
    if end > crc_at {
        return Err(());
    }
    let mut r = Bits { b: s, at: 14 * 8, end };
    let mut st = States::nothing(Some(cmd));
    match cmd {
        command::SPLICE_INSERT => {
            let (cancel, out, id, duration) = splice_insert(&mut r)?;
            st.insert = Some((cancel, out));
            st.id = Some(id);
            st.duration = duration;
        }
        command::TIME_SIGNAL => splice_time(&mut r)?,
        command::SPLICE_NULL => {}
        _ => r.at = end * 8,
    }
    let cmd_end = r.byte_pos();
    if cmd_len != 0xFFF && cmd_end != end {
        return Err(());
    }
    if cmd_end + 2 > crc_at {
        return Err(());
    }
    let loop_len = u16::from_be_bytes([s[cmd_end], s[cmd_end + 1]]) as usize;
    let mut p = cmd_end + 2;
    if p + loop_len != crc_at {
        return Err(());
    }
    let mut segs = Vec::new();
    while p < crc_at {
        if p + 2 > crc_at {
            return Err(());
        }
        let (tag, len) = (s[p], s[p + 1] as usize);
        let body = p + 2;
        if body + len > crc_at {
            return Err(());
        }
        if tag == 0x02 && len >= 4 && &s[body..body + 4] == b"CUEI" {
            segs.push(segmentation(s, body + 4, body + len)?);
        }
        p = body + len;
    }
    let live = |x: &&Segmentation| !x.cancelled;
    if cmd == command::TIME_SIGNAL
        && let Some(o) = segs.iter().filter(live).find(|x| OPENING.contains(&x.type_id))
    {
        st.id = Some(o.event_id);
        st.duration = o.duration;
    }
    st.slots = segs.iter().filter(live).find(|x| PLACEMENT.contains(&x.type_id)).and_then(|x| x.sub).map(|(_, e)| e as u64).filter(|e| *e > 0);
    Ok(st)
}

// ---------------------------------------------------------------------------------------
// Writing, for the corpus
// ---------------------------------------------------------------------------------------

/// A big-endian bit writer.
#[derive(Default)]
struct Out {
    b: Vec<u8>,
    bits: usize,
}

impl Out {
    fn put(&mut self, n: usize, v: u64) {
        for i in (0..n).rev() {
            if self.bits.is_multiple_of(8) {
                self.b.push(0);
            }
            let bit = ((v >> i) & 1) as u8;
            let last = self.b.len() - 1;
            self.b[last] |= bit << (7 - self.bits % 8);
            self.bits += 1;
        }
    }
}

/// A segmentation descriptor to write.
#[derive(Clone, Debug)]
pub struct SegmentationSpec {
    pub event_id: u32,
    pub cancelled: bool,
    /// In 90 kHz ticks.
    pub duration: Option<u64>,
    pub type_id: u8,
    pub sub: Option<(u8, u8)>,
}

/// A command to write.
#[derive(Clone, Debug)]
pub enum Command {
    /// `splice_insert()`: id, cancel, out_of_network, and the break duration in ticks.
    Insert { id: u32, cancel: bool, out: bool, duration: Option<u64> },
    /// `time_signal()` at a `pts_time`.
    TimeSignal { pts: u64 },
}

fn segmentation_bytes(d: &SegmentationSpec) -> Vec<u8> {
    let mut o = Out::default();
    o.put(32, u32::from_be_bytes(*b"CUEI") as u64);
    o.put(32, d.event_id as u64);
    o.put(1, d.cancelled as u64);
    o.put(1, 1);
    o.put(6, 0x3F);
    if !d.cancelled {
        o.put(1, 1); // program_segmentation_flag
        o.put(1, d.duration.is_some() as u64);
        o.put(1, 1); // delivery_not_restricted_flag
        o.put(5, 0x1F);
        if let Some(t) = d.duration {
            o.put(40, t);
        }
        o.put(8, 0); // segmentation_upid_type: not used
        o.put(8, 0); // segmentation_upid_length
        o.put(8, d.type_id as u64);
        o.put(8, 1); // segment_num
        o.put(8, 1); // segments_expected
        if let Some((n, e)) = d.sub {
            o.put(8, n as u64);
            o.put(8, e as u64);
        }
    }
    let mut v = vec![0x02, o.b.len() as u8];
    v.extend(o.b);
    v
}

/// A section of one command and its segmentation descriptors, with its CRC.
pub fn write(cmd: &Command, descriptors: &[SegmentationSpec]) -> Vec<u8> {
    let mut c = Out::default();
    let ty = match cmd {
        Command::Insert { id, cancel, out, duration } => {
            c.put(32, *id as u64);
            c.put(1, *cancel as u64);
            c.put(7, 0x7F);
            if !cancel {
                c.put(1, *out as u64);
                c.put(1, 1); // program_splice_flag
                c.put(1, duration.is_some() as u64);
                c.put(1, 1); // splice_immediate_flag
                c.put(1, 1); // event_id_compliance_flag
                c.put(3, 0x7);
                if let Some(t) = duration {
                    c.put(1, 1);
                    c.put(6, 0x3F);
                    c.put(33, *t);
                }
                c.put(16, 1); // unique_program_id
                c.put(8, 0); // avail_num
                c.put(8, 0); // avails_expected
            }
            command::SPLICE_INSERT
        }
        Command::TimeSignal { pts } => {
            c.put(1, 1);
            c.put(6, 0x3F);
            c.put(33, *pts);
            command::TIME_SIGNAL
        }
    };
    let mut loop_ = Vec::new();
    for d in descriptors {
        loop_.extend(segmentation_bytes(d));
    }
    let mut body = Out::default();
    body.put(8, 0); // protocol_version
    body.put(1, 0); // encrypted_packet
    body.put(6, 0); // encryption_algorithm
    body.put(33, 0); // pts_adjustment
    body.put(8, 0xFF); // cw_index
    body.put(12, 0xFFF); // tier
    body.put(12, c.b.len() as u64);
    body.put(8, ty as u64);
    let mut rest = body.b;
    rest.extend(&c.b);
    rest.extend((loop_.len() as u16).to_be_bytes());
    rest.extend(loop_);
    let section_length = rest.len() + 4;
    let mut s = vec![0xFC, 0x30 | ((section_length >> 8) as u8 & 0x0F), section_length as u8];
    s.extend(rest);
    let crc = crc32(&s);
    s.extend(crc.to_be_bytes());
    s
}

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

    /// ANSI/SCTE 35-1 §14.1's sample, as cue-injection §5.7 holds it: a `time_signal()` with a
    /// Provider Placement Opportunity Start of 307 s.
    const SAMPLE: &str = "fc3034000000000000fffff00506fe72bd0050001e021c435545494800008e7fcf0001a599b00808000000002ca0a18a3402009ac9d17e";

    fn unhex(s: &str) -> Vec<u8> {
        (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
    }

    #[test]
    fn the_crc_of_the_standards_sample() {
        let s = unhex(SAMPLE);
        let n = s.len();
        assert_eq!(crc32(&s[..n - 4]), u32::from_be_bytes(s[n - 4..].try_into().unwrap()));
    }

    #[test]
    fn the_standards_sample_states_its_opportunity() {
        let st = read(&unhex(SAMPLE)).expect("the sample reads");
        assert_eq!(st.command, Some(command::TIME_SIGNAL));
        assert_eq!(st.id, Some(0x4800_008e));
        // 307 s at 90 kHz.
        assert_eq!(st.duration, Some(307 * 90_000));
        // Its descriptor (28 bytes) ends at `segments_expected`: it carries no sub-segment
        // fields, so it states no slots.
        assert_eq!(st.slots, None);
    }

    #[test]
    fn written_sections_read_back() {
        let s = write(&Command::Insert { id: 7, cancel: false, out: true, duration: Some(5_400_000) }, &[]);
        let st = read(&s).unwrap();
        assert_eq!((st.id, st.duration, st.slots, st.insert), (Some(7), Some(5_400_000), None, Some((false, true))));
        let d = SegmentationSpec { event_id: 9, cancelled: false, duration: Some(8_100_000), type_id: 0x34, sub: Some((1, 3)) };
        let s = write(&Command::TimeSignal { pts: 12_345 }, &[d]);
        let st = read(&s).unwrap();
        assert_eq!((st.id, st.duration, st.slots), (Some(9), Some(8_100_000), Some(3)));
    }

    #[test]
    fn a_cancelled_descriptor_states_nothing() {
        let c = SegmentationSpec { event_id: 3, cancelled: true, duration: None, type_id: 0, sub: None };
        let d = SegmentationSpec { event_id: 4, cancelled: false, duration: None, type_id: 0x36, sub: Some((1, 2)) };
        let st = read(&write(&Command::TimeSignal { pts: 0 }, &[c, d])).unwrap();
        assert_eq!((st.id, st.slots), (Some(4), Some(2)));
    }

    #[test]
    fn a_damaged_section_is_refused() {
        let s = write(&Command::TimeSignal { pts: 1 }, &[]);
        let mut crc = s.clone();
        let n = crc.len();
        crc[n - 1] ^= 1;
        assert!(read(&crc).is_err());
        assert!(read(&s[..19]).is_err());
        // A descriptor loop that runs past the CRC, the CRC made good again.
        let mut long = s.clone();
        let at = long.len() - 6;
        long[at + 1] = 9;
        let n = long.len();
        let c = crc32(&long[..n - 4]);
        long[n - 4..].copy_from_slice(&c.to_be_bytes());
        assert!(read(&long).is_err());
    }
}
