use crate::*;
use tokio::sync::{broadcast, watch};

pub struct ConnectionManager {
    state_watch: watch::Receiver<State>,
    notification_rx: broadcast::Receiver<JackNotification>,
    jack_client: jack::Client,
    jack_client_name: String,
}

impl ConnectionManager {
    pub fn new(
        state_watch: watch::Receiver<State>,
        notification_rx: broadcast::Receiver<JackNotification>,
        jack_client_name: String,
    ) -> Result<Self> {
        let (jack_client, _status) = jack::Client::new(
            &format!("{}_connector", jack_client_name),
            jack::ClientOptions::NO_START_SERVER,
        )
        .map_err(|_| LooperError::JackConnection(std::panic::Location::caller()))?;

        Ok(Self {
            state_watch,
            notification_rx,
            jack_client,
            jack_client_name,
        })
    }

    pub async fn run(&mut self) -> Result<()> {
        self.restore_connections().await;
        while let Ok(notification) = self.notification_rx.recv().await {
            if let JackNotification::PortRegistered { .. } = notification {
                self.restore_connections().await;
            }
        }
        Ok(())
    }

    async fn restore_connections(&self) {
        let state = self.state_watch.borrow();

        for external_port in &state.connections.midi_in {
            let our_port = format!("{}:midi_in", self.jack_client_name);
            let result = self
                .jack_client
                .connect_ports_by_name(external_port, &our_port);
            if let Ok(_) = result {
                log::info!("Connected {} -> {}", external_port, our_port);
            }
        }

        for external_port in &state.connections.midi_out {
            let our_port = format!("{}:midi_out", self.jack_client_name);
            let result = self
                .jack_client
                .connect_ports_by_name(&our_port, external_port);
            if let Ok(_) = result {
                log::info!("Connected {} -> {}", our_port, external_port);
            }
        }

        for external_port in &state.connections.audio_in {
            let our_port = format!("{}:audio_in", self.jack_client_name);
            let result = self
                .jack_client
                .connect_ports_by_name(external_port, &our_port);
            if let Ok(_) = result {
                log::info!("Connected {} -> {}", external_port, our_port);
            }
        }

        for external_port in &state.connections.audio_out {
            let our_port = format!("{}:audio_out", self.jack_client_name);
            let result = self
                .jack_client
                .connect_ports_by_name(&our_port, external_port);
            if let Ok(_) = result {
                log::info!("Connected {} -> {}", our_port, external_port);
            }
        }

        for external_port in &state.connections.click_track_out {
            let our_port = format!("{}:click_track", self.jack_client_name);
            let result = self
                .jack_client
                .connect_ports_by_name(&our_port, external_port);
            if let Ok(_) = result {
                log::info!("Connected {} -> {}", our_port, external_port);
            }
        }
    }
}
