#![warn(clippy::all, clippy::pedantic)]

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
mod command;
mod telemetry;

use crate::command::CommandHandler;
use crate::telemetry::TelemetryHandler;
use anyhow::{Result, anyhow, bail};
use api::client::Client;
use futures::executor::block_on;
use futures::future::{Either, select};
use log::{info, warn};
use nautilus_common::add_ctrlc_handler_cancel;
use std::sync::Arc;
use std::thread::{Builder, scope};
use tokio::net::UdpSocket;
use tokio::pin;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;

/// Run the Ground Software
///
/// # Errors
/// If any unhandled error occurred in the Ground Software
pub fn run() -> Result<()> {
    info!(
        "Project Nautilus Ground Software {}",
        env!("CARGO_PKG_VERSION")
    );

    let udp_shutdown = CancellationToken::new();
    let cancel = udp_shutdown.child_token();
    add_ctrlc_handler_cancel(cancel.clone())?;

    let (udp_thread, udp) = {
        let udp_shutdown = udp_shutdown.clone();
        let (udp_tx, udp) = tokio::sync::oneshot::channel();

        let udp_thread = Builder::new()
            .name("flight-udp-connection-handler".to_string())
            .spawn(move || {
                let runtime = tokio::runtime::Builder::new_current_thread()
                    .enable_all()
                    .build()?;

                runtime.block_on(async {
                    let udp = UdpSocket::bind("0.0.0.0:14000").await?;
                    udp_tx
                        .send(udp)
                        .map_err(|_| anyhow!("Couldn't complete UDP establish"))?;
                    Ok(()) as Result<()>
                })?;

                runtime.block_on(udp_shutdown.cancelled());

                Ok(()) as Result<()>
            })?;

        let f1 = cancel.cancelled();
        pin!(f1);
        let udp = block_on(async {
            let f2 = udp;
            select(f1, f2).await
        });
        let udp = match udp {
            Either::Left(_) => bail!("Cancelled Before UDP established"),
            Either::Right(x) => x.0?,
        };
        (udp_thread, udp)
    };

    let flight_addr = RwLock::new(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0));
    let lock = flight_addr.blocking_write();

    scope(|scope| {
        let client = Arc::new(Client::connect("ws://localhost:8080/backend")?);

        let tlm = TelemetryHandler::new(client.clone(), &flight_addr, lock, &udp, cancel.clone());
        let cmd = CommandHandler::new(client, &flight_addr, &udp, cancel.clone());

        let tlm_thread = Builder::new()
            .name("telemetry-handler".to_string())
            .spawn_scoped(scope, move || tlm.run())?; // move to take ownership (drop when exited)

        let cmd_thread = Builder::new()
            .name("command-handler".to_string())
            .spawn_scoped(scope, move || cmd.run())?;

        // Force the thread panics into anyhow errors
        tlm_thread.join().map_err(|e| anyhow!("{e:?}"))??;
        cmd_thread.join().map_err(|e| anyhow!("{e:?}"))??;
        Ok(()) as Result<()>
    })?;

    info!("Shutting Down");

    udp_shutdown.cancel();
    udp_thread.join().map_err(|e| anyhow!("{e:?}"))??;

    Ok(())
}
