#[cfg(target_os = "linux")]
mod dbus;

#[cfg(target_os = "macos")]
mod xpc;

use log::LevelFilter;
use std::future;

use kordophoned::daemon::Daemon;

fn initialize_logging() {
    // Weird: is this the best way to do this?
    let log_level = std::env::var("RUST_LOG")
        .map(|s| s.parse::<LevelFilter>().unwrap_or(LevelFilter::Info))
        .unwrap_or(LevelFilter::Info);

    env_logger::Builder::from_default_env()
        .format_timestamp_millis()
        .filter_level(log_level)
        .init();
}

#[cfg(target_os = "linux")]
async fn start_ipc_agent(daemon: &mut Daemon) {
    use dbus::agent::DBusAgent;

    // Start the D-Bus agent (events in, signals out).
    let agent = DBusAgent::new(daemon.event_sender.clone(), daemon.obtain_signal_receiver());
    tokio::spawn(async move {
        agent.run().await;
    });
}

#[cfg(target_os = "macos")]
async fn start_ipc_agent(daemon: &mut Daemon) {
    // Start the macOS XPC agent (events in, signals out) on a dedicated thread.
    let agent =
        xpc::agent::XpcAgent::new(daemon.event_sender.clone(), daemon.obtain_signal_receiver());
    std::thread::spawn(move || {
        // Use a single-threaded Tokio runtime for the XPC agent.
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("Unable to create tokio runtime for XPC agent");
        rt.block_on(agent.run());
    });
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
async fn start_ipc_agent(daemon: &mut Daemon) {
    panic!("Unsupported IPC platform");
}

#[tokio::main]
async fn main() {
    initialize_logging();

    // Create the daemon
    let mut daemon = Daemon::new()
        .map_err(|e| {
            log::error!("Failed to initialize daemon: {}", e);
            std::process::exit(1);
        })
        .unwrap();

    // Start the IPC agent.
    start_ipc_agent(&mut daemon).await;

    // Run the main daemon loop.
    daemon.run().await;

    // Keep the process alive as long as any background tasks are running.
    future::pending::<()>().await;
}
