mod bridge;
mod cli;
mod message;
mod pty;
mod serial;
mod transport;

use anyhow::Result;
use clap::Parser;
use cli::{Cli, Commands};
use tracing::info;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();

    match cli.command {
        Commands::Serve {
            port,
            baud,
            channel,
            domain,
        } => run_serve(port, baud, channel, domain).await,
        Commands::Attach {
            channel,
            link,
            domain,
        } => run_attach(channel, link, domain).await,
        Commands::List { domain, wait } => run_list(domain, wait).await,
    }
}

async fn run_serve(
    port: std::path::PathBuf,
    baud: u32,
    channel: String,
    domain: u16,
) -> Result<()> {
    info!(
        port = %port.display(),
        baud,
        channel,
        domain,
        "starting serve"
    );

    let participant = transport::DdsParticipant::new(domain)?;
    let dds_channel = transport::DdsChannel::open(&participant.participant, &channel)?;
    let serial = serial::open_serial(&port, baud).await?;

    tokio::select! {
        res = bridge::run_serve_bridge(serial, dds_channel) => res?,
        res = transport::wait_for_shutdown() => {
            res?;
        }
    }

    Ok(())
}

async fn run_attach(
    channel: String,
    link: std::path::PathBuf,
    domain: u16,
) -> Result<()> {
    info!(channel, link = %link.display(), domain, "starting attach");

    let endpoint = pty::create_pty_endpoint(&link)?;
    let link_path = endpoint.link_path.clone();
    let participant = transport::DdsParticipant::new(domain)?;
    let dds_channel = transport::DdsChannel::open(&participant.participant, &channel)?;

    let bridge_result = tokio::select! {
        res = bridge::run_attach_bridge(endpoint.async_io, dds_channel) => res,
        res = transport::wait_for_shutdown() => {
            res?;
            pty::cleanup_link(&link_path).await?;
            return Ok(());
        }
    };

    bridge_result?;
    pty::cleanup_link(&link_path).await?;

    Ok(())
}

async fn run_list(domain: u16, wait: u64) -> Result<()> {
    let participant = transport::DdsParticipant::new(domain)?;
    let channels = transport::discover_channels(&participant.participant, wait).await?;

    if channels.is_empty() {
        println!("No super_cereal channels discovered.");
    } else {
        for channel in channels {
            println!("{channel}");
        }
    }

    Ok(())
}
