use pipewire::{context::Context, main_loop::MainLoop};
use std::{
    net::{Ipv6Addr, SocketAddrV6},
    sync::{Arc, RwLock},
};
use tokio::task::spawn_blocking;
use warp::{serve, Filter};

struct State_ {
    device_list: Vec<String>,
}

#[derive(Clone)]
struct State {
    internal: Arc<RwLock<State_>>,
}

impl State {
    fn new() -> State {
        let internal = State_ {
            device_list: vec![],
        };
        State {
            internal: Arc::new(RwLock::new(internal)),
        }
    }

    fn add_audio(&self, device: String) {
        let mut st = self.internal.write().unwrap();
        (*st).device_list.push(device);
    }

    fn audio_devices(&self) -> Vec<String> {
        let st = self.internal.read().unwrap();
        (*st).device_list.clone()
    }
}

impl Default for State {
    fn default() -> State {
        State::new()
    }
}

async fn server_main(state: State) {
    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 state = state.clone();
        move || {
            let devices = state.audio_devices();
            serde_json::to_string(&devices).unwrap()
        }
    });

    let routes = root.or(list_output_devices);

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

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

fn pipewire_loop(state: State) -> 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 state = state.clone();
            move |global_data| {
                if let Some(props) = global_data.props {
                    handle_add_audio_device(state.clone(), props);
                }
            }
        })
        .register();

    mainloop.run();

    Ok(())
}

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

#[tokio::main]
async fn main() {
    let state = State::default();

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

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