use std::{
    convert::Infallible,
    net::{Ipv6Addr, SocketAddrV6},
    path::PathBuf,
    sync::Arc,
};

use app::App;
use audio_control::{AudioControl, GStreamerBackend};
use pipewire::{context::Context, main_loop::MainLoop};
use serde::Deserialize;
use tokio::task::spawn_blocking;
use warp::{serve, Filter};

mod app;
mod audio_control;
mod types;

#[derive(Deserialize)]
struct PlayTrackParams {
    track_name: String,
}

fn with_app(app: Arc<App>) -> impl Filter<Extract = (Arc<App>,), Error = Infallible> + Clone {
    warp::any().map(move || app.clone())
}

async fn server_main(app: Arc<App>) {
    let localhost: Ipv6Addr = "::1".parse().unwrap();
    let server_addr = SocketAddrV6::new(localhost, 3001, 0, 0);

    let root = warp::path!().map(|| "ok".to_string());
    let list_output_devices = warp::path!("output_devices").map({
        let app = app.clone();
        move || {
            let devices = app.audio_devices();
            serde_json::to_string(&devices).unwrap()
        }
    });

    /*
    let list_tracks = warp::path!("tracks").map({
        let app = app.clone();
        move || serde_json::to_string(&app.tracks()).unwrap()
    });
    */

    let enable_track = warp::put()
        .and(warp::path!("playing"))
        .and(warp::body::json())
        .and(with_app(app.clone()))
        .then(|params: PlayTrackParams, app: Arc<App>| async move {
            println!("enable track");
            let _ = app.enable_track(PathBuf::from(params.track_name)).await;
            "".to_owned()
        });

    let disable_track = warp::delete()
        .and(warp::path!("playing"))
        .and(warp::body::json())
        .and(with_app(app.clone()))
        .then(|params: PlayTrackParams, app: Arc<App>| async move {
            let _ = app.disable_track(&params.track_name);
            "".to_owned()
        });

    let play_all = warp::post()
        .and(warp::path!("play"))
        .and(with_app(app.clone()))
        .then({
            |app: Arc<App>| async move {
                println!("play_all");
                let _ = app.play().await;
                "".to_owned()
            }
        });

    let stop_all = warp::post()
        .and(warp::path!("stop"))
        .and(with_app(app.clone()))
        .then({
            |app: Arc<App>| async move {
                let _ = app.stop().await;
                "".to_owned()
            }
        });

    let pause = warp::post()
        .and(warp::path!("pause"))
        .and(with_app(app.clone()))
        .then({
            |app: Arc<App>| async move {
                let _ = app.pause().await;
                "".to_owned()
            }
        });

    let now_playing = warp::path!("playing").map({
        let app = app.clone();
        move || serde_json::to_string(&app.playing()).unwrap()
    });

    let routes = root
        .or(list_output_devices)
        // .or(list_tracks)
        .or(enable_track)
        .or(disable_track)
        .or(play_all)
        .or(stop_all)
        .or(pause)
        .or(now_playing);

    serve(routes).run(server_addr).await;
}

fn handle_add_audio_device(app: App, props: &pipewire::spa::utils::dict::DictRef) {
    if props.get("media.class") == Some("Audio/Sink") {
        if let Some(device_name) = props.get("node.description") {
            app.add_audio(device_name.to_owned());
        }
    }
}

/*
fn pipewire_loop(app: App) -> Result<(), Box<dyn std::error::Error>> {
    let mainloop = MainLoop::new(None)?;
    let context = Context::new(&mainloop)?;
    let core = context.connect(None)?;
    let registry = core.get_registry()?;

    let _listener = registry
        .add_listener_local()
        .global({
            let app = app.clone();
            move |global_data| {
                if let Some(props) = global_data.props {
                    handle_add_audio_device(app.clone(), props);
                }
            }
        })
        .register();

    mainloop.run();

    Ok(())
}
*/

/*
fn pipewire_main(state: App) {
    pipewire_loop(state).expect("pipewire should not error");
}
*/

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

    let (audio_control_tx, audio_control_rx) = tokio::sync::mpsc::channel(5);
    let (audio_status_tx, audio_status_rx) = tokio::sync::mpsc::channel(5);

    let app = Arc::new(App::new(audio_control_tx, audio_status_rx));
    let audio_controller = Arc::new(AudioControl::new(GStreamerBackend::default()));
    tokio::spawn({
        let audio_controller = audio_controller.clone();
        async move { audio_controller.listen(audio_control_rx).await }
    });
    tokio::spawn({
        let audio_controller = audio_controller.clone();
        async move { audio_controller.report(audio_status_tx).await }
    });

    /*
    spawn_blocking({
        let app = app.clone();
        move || pipewire_main(state)
    });
    */

    server_main(app.clone()).await;
}