arib-cli 0.3.0

Reads the signalling of ARIB broadcasts, as an example of the arib crate
use std::collections::BTreeMap;
use std::ops::ControlFlow;
use std::time::Duration;

use clap::Args;

use crate::component::Stream;
use crate::input::InputArgs;
use crate::json;
use crate::print::{or_unknown, print_event, print_service, print_stream};
use crate::si::{self, Event, Network, Service, Si};

/// Shows the network, the services of the stream, and what is on air on each.
#[derive(Clone, Debug, Args)]
pub struct Options {
    #[command(flatten)]
    input: InputArgs,

    /// Seconds to wait for the tables before printing what was found.
    #[arg(long, default_value_t = 10)]
    timeout: u64,

    /// Prints what was found as JSON.
    #[arg(long)]
    json: bool,
}

#[derive(Default)]
struct Status {
    network: Option<Network>,
    /// The stream being read, and the services it carries.
    services: Option<(u16, Vec<Service>)>,
    present_events: BTreeMap<u16, Event>,
    /// The streams of each service, as its PMT or MPT lists them.
    streams: BTreeMap<u16, Vec<Stream>>,
}

impl Status {
    fn read(&mut self, si: Si) -> ControlFlow<()> {
        match si {
            Si::Network(network) => self.network = Some(network),
            Si::Services(services) if services.actual => {
                self.services = Some((services.stream_id, services.services));
            }
            Si::Streams(streams) => {
                self.streams.insert(streams.service_id, streams.streams);
            }
            Si::Events(events) if events.present_following && events.section_number == 0 => {
                if let Some(event) = events.events.into_iter().next() {
                    self.present_events.insert(events.service_id, event);
                }
            }
            _ => {}
        }

        if self.is_complete() {
            ControlFlow::Break(())
        } else {
            ControlFlow::Continue(())
        }
    }

    fn is_complete(&self) -> bool {
        let Some((_, services)) = &self.services else {
            return false;
        };

        self.network.is_some()
            && services.iter().all(|service| {
                self.present_events.contains_key(&service.id)
                    && self.streams.contains_key(&service.id)
            })
    }

    fn to_json(&self) -> serde_json::Value {
        let (stream_id, services) = self
            .services
            .as_ref()
            .map_or((None, &[][..]), |(stream_id, services)| {
                (Some(stream_id), &services[..])
            });
        let services: Vec<_> = services
            .iter()
            .map(|service| {
                let event = self.present_events.get(&service.id);
                let streams: Vec<_> = self
                    .streams
                    .get(&service.id)
                    .into_iter()
                    .flatten()
                    .map(|stream| json::stream(stream, stream_details(stream, event)))
                    .collect();
                let mut value = json::service(service);
                value["streams"] = streams.into();
                value["event"] = event.map(json::event).into();
                value
            })
            .collect();

        serde_json::json!({
            "network": self.network.as_ref().map(json::network),
            "stream_id": stream_id,
            "services": services,
        })
    }

    fn print(&self, verbose: bool) {
        match &self.network {
            Some(network) => println!("Network: {:#06X} {}", network.id, or_unknown(&network.name)),
            None => println!("Network: (not found)"),
        }

        let Some((stream_id, services)) = &self.services else {
            println!("Services: (not found)");
            return;
        };

        println!("Stream: {stream_id:#06X}");
        println!("Services:");
        for service in services {
            print_service(service, 2);

            let event = self.present_events.get(&service.id);
            for stream in self.streams.get(&service.id).into_iter().flatten() {
                print_stream(stream, stream_details(stream, event), 4);
            }

            match event {
                Some(event) => print_event(event, 4, verbose),
                None => println!("    (no event on air)"),
            }
        }
    }
}

/// The components of the event on air tell of a stream as it is now, where MMT also carries them
/// in the MPT for the stream itself.
fn stream_details<'a>(stream: &'a Stream, event: Option<&'a Event>) -> &'a [String] {
    event
        .and_then(|event| {
            event
                .components
                .iter()
                .find(|component| Some(component.component_tag) == stream.component_tag)
        })
        .map_or(&stream.details, |component| &component.details)
}

pub fn run(options: &Options, verbose: bool) -> anyhow::Result<()> {
    let mut status = Status::default();
    si::read(
        options.input.open()?,
        Duration::from_secs(options.timeout),
        |si| status.read(si),
    )?;

    if options.json {
        println!("{}", status.to_json());
    } else {
        status.print(verbose);
    }

    Ok(())
}