use super::webbluetooth_hardware::WebBluetoothHardwareConnector;

use buttplug_core::ButtplugResultFuture;
use buttplug_server_device_config::ProtocolCommunicationSpecifier;
use buttplug_server::device::hardware::communication::{
  HardwareCommunicationManager, HardwareCommunicationManagerBuilder,
  HardwareCommunicationManagerEvent,
};
use futures::future;
use tokio::sync::mpsc::Sender;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::BluetoothDevice;
use crate::create_test_dcm;

#[derive(Default)]
pub struct WebBluetoothCommunicationManagerBuilder {
}

impl HardwareCommunicationManagerBuilder for WebBluetoothCommunicationManagerBuilder {
  fn finish(&mut self, sender: Sender<HardwareCommunicationManagerEvent>) -> Box<dyn HardwareCommunicationManager> {
    Box::new(WebBluetoothCommunicationManager {
      sender,
    })
  }
}

pub struct WebBluetoothCommunicationManager {
  sender: Sender<HardwareCommunicationManagerEvent>,
}

#[wasm_bindgen]
extern "C" {
  // Use `js_namespace` here to bind `console.log(..)` instead of just
  // `log(..)`
  #[wasm_bindgen(js_namespace = console)]
  fn log(s: &str);
}

impl HardwareCommunicationManager for WebBluetoothCommunicationManager {
  fn name(&self) -> &'static str {
    "WebBluetoothCommunicationManager"
  }

  fn can_scan(&self) -> bool {
    true
  }

  fn start_scanning(&mut self) -> ButtplugResultFuture {
    info!("WebBluetooth manager scanning");
    let sender_clone = self.sender.clone();
    spawn_local(async move {
      // Build the filter block
      let nav = web_sys::window().unwrap().navigator();
      if nav.bluetooth().is_none() {
        error!("WebBluetooth is not supported on this browser");
        return;
      }
      info!("WebBluetooth supported by browser, continuing with scan.");
      // HACK: As of buttplug v5, we can't just create a HardwareCommunicationManager anymore. This is
      // using a test method to create a filled out DCM, which will work for now because there's no
      // way for anyone to add device configurations through FFI yet anyways.
      let config_manager = create_test_dcm(false);
      let options = web_sys::RequestDeviceOptions::new();
      let mut filters = Vec::new();
      let mut optional_services = Vec::new();
      for vals in config_manager.base_communication_specifiers().iter() {
        for config in vals.1.iter() {
          if let ProtocolCommunicationSpecifier::BluetoothLE(btle) = &config {
            for name in btle.names() {
              let filter = web_sys::BluetoothLeScanFilterInit::new();
              if name.contains("*") {
                let mut name_clone = name.clone();
                name_clone.pop();
                filter.set_name_prefix(&name_clone);
              } else {
                filter.set_name(&name);
              }
              filters.push(filter);
            }
            for (service, _) in btle.services() {
              optional_services.push(js_sys::JsString::from(service.to_string()));
            }
          }
        }
      }
      options.set_filters(&filters);
      options.set_optional_services(&optional_services);
      let nav = web_sys::window().unwrap().navigator();
      //nav.bluetooth().get_availability();
      //JsFuture::from(nav.bluetooth().request_device()).await;
      match JsFuture::from(nav.bluetooth().unwrap().request_device(&options)).await {
        Ok(device) => {
          let bt_device = BluetoothDevice::from(device);
          if bt_device.name().is_none() {
            return;
          }
          let name = bt_device.name().unwrap();
          let address = bt_device.id();
          let device_creator = Box::new(WebBluetoothHardwareConnector::new(bt_device));
          if sender_clone
            .send(HardwareCommunicationManagerEvent::DeviceFound {
              name,
              address,
              creator: device_creator,
            })
            .await
            .is_err()
          {
            error!("Device manager receiver dropped, cannot send device found message.");
          } else {
            info!("WebBluetooth device found.");
          }
        }
        Err(e) => {
          error!("Error while trying to start bluetooth scan: {:?}", e);
        }
      };
      let _ = sender_clone
        .send(HardwareCommunicationManagerEvent::ScanningFinished)
        .await;
    });
    Box::pin(future::ready(Ok(())))
  }

  fn stop_scanning(&mut self) -> ButtplugResultFuture {
    Box::pin(future::ready(Ok(())))
  }
}
