use std::sync::{Arc, Mutex};

use futures::future::join_all;
use tokio::sync::Semaphore;

use crate::{doctor::Doctor, host::Hosts, media::Media};

pub struct BdSystem<T, S> {
    doctor: Arc<T>,
    hosts: Hosts,
    media: Arc<Mutex<S>>,
    concurrent_tests: usize,
}

impl<T: Doctor + Sync + Send + 'static, S: Media + Send + 'static> BdSystem<T, S> {
    pub fn new(doctor: T, hosts: Hosts, media: S, concurrent_tests: usize) -> Self {
        Self {
            doctor: Arc::new(doctor),
            hosts,
            media: Arc::new(Mutex::new(media)),
            concurrent_tests,
        }
    }

    pub async fn run(&self) -> bool {
        let semaphore = Arc::new(Semaphore::new(self.concurrent_tests));
        let mut handles = Vec::new();
        for host in self.hosts.all().to_vec() {
            let permit = semaphore.clone().acquire_owned().await.unwrap();
            let doctor = self.doctor.clone();
            let media = self.media.clone();
            handles.push(tokio::task::spawn_blocking(move || {
                let _permit = permit;
                host.test_by(doctor.as_ref())
                    .print_to(&mut *media.lock().unwrap());
            }));
        }
        join_all(handles).await;
        true
    }
}
