use std::{
  cell::RefCell,
  error::Error,
  fs::File,
  io::Write,
  os::unix::io::FromRawFd,
  rc::Rc,
  sync::{
    atomic::{AtomicBool, Ordering},
    Arc, Mutex, RwLock,
  },
  thread,
};
use log::{debug, error};
use wayland_client::{global_filter, protocol::wl_seat::WlSeat, Display, GlobalManager, Main};
use wayland_protocols::wlr::unstable::data_control::v1::client::{
  zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, zwlr_data_control_source_v1::Event,
};
use zeroize::Zeroize;
use crate::api::{ClipboardProviding, EventData, EventHub};
use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
use super::{ClipboardCommon, ClipboardError, ClipboardResult, SelectionProvider};
const TEXT_MIMES: &[&str] = &[
  "text/plain;charset=utf-8",
  "text/plain",
  "STRING",
  "UTF8_STRING",
  "TEXT",
];
struct Context {
  display: Display,
  open: AtomicBool,
  cancel: AtomicBool,
  provider_holder: RwLock<SelectionProviderHolder>,
}
impl Context {
  fn new<T>(display: Display, provider: T) -> Self
  where
    T: SelectionProvider + 'static,
  {
    Context {
      display,
      open: AtomicBool::new(false),
      cancel: AtomicBool::new(false),
      provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
    }
  }
  fn is_open(&self) -> bool {
    self.open.load(Ordering::Relaxed)
  }
  fn currently_providing(&self) -> Option<ClipboardProviding> {
    self.provider_holder.read().ok()?.current_selection()
  }
  fn provide_next(&self) {
    if let Ok(mut provider_holder) = self.provider_holder.write() {
      provider_holder.get_value();
    }
  }
  fn destroy(&self) {
    self.cancel.store(true, Ordering::Relaxed)
  }
}
pub struct Clipboard {
  context: Arc<Context>,
  handle: Mutex<Option<thread::JoinHandle<()>>>,
}
impl ClipboardCommon for Clipboard {
  fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
  where
    T: SelectionProvider + Clone + 'static,
  {
    let display = Display::connect_to_env()?;
    debug!("Got display: {}", display.id());
    match selection_provider.current_selection() {
      Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
      None => return Err(ClipboardError::Other("Empty provider".to_string())),
    };
    let context = Arc::new(Context::new(display, selection_provider));
    let handle = thread::spawn({
      let cloned = context.clone();
      move || {
        if let Err(err) = try_run(cloned.clone()) {
          cloned.open.store(false, Ordering::Relaxed);
          error!("Wayland clipboard error: {}", err);
        }
      }
    });
    Ok(Clipboard {
      context,
      handle: Mutex::new(Some(handle)),
    })
  }
  fn is_open(&self) -> bool {
    self.context.is_open()
  }
  fn currently_providing(&self) -> Option<ClipboardProviding> {
    self.context.currently_providing()
  }
  fn provide_next(&self) {
    self.context.provide_next()
  }
  fn destroy(&self) {
    self.context.destroy()
  }
  fn wait(&self) -> ClipboardResult<()> {
    let mut maybe_handle = self.handle.lock()?;
    if let Some(handle) = maybe_handle.take() {
      handle
        .join()
        .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
    }
    Ok(())
  }
}
fn try_run(context: Arc<Context>) -> Result<(), Box<dyn Error>> {
  let mut queue = context.display.create_event_queue();
  let display = context.display.attach(queue.token());
  let seats = Rc::new(RefCell::new(vec![]));
  let mut devices = vec![];
  let seats_cloned = seats.clone();
  let manager = GlobalManager::new_with_cb(
    &display,
    global_filter!([WlSeat, 2, move |seat: Main<WlSeat>, _: DispatchData| {
      seats_cloned.borrow_mut().push(seat);
    }]),
  );
  queue.sync_roundtrip(&mut (), |_, _, _| {})?;
  debug!("Seats: {:?}", seats.borrow());
  debug!("Globals: {:?}", manager.list());
  let clipboard_manager: Main<ZwlrDataControlManagerV1> = manager.instantiate_exact(1)?;
  let data_source = clipboard_manager.create_data_source();
  let context_cloned = context.clone();
  data_source.quick_assign(move |_, event, _| match event {
    Event::Send { mime_type, fd } if TEXT_MIMES.contains(&mime_type.as_str()) => {
      debug!("Event send: {} {}", mime_type, fd);
      match context_cloned.provider_holder.write() {
        Ok(mut selection_provider) => {
          if let Some(mut content) = selection_provider.get_value() {
            let mut f = unsafe { File::from_raw_fd(fd) };
            f.write_all(content.as_bytes()).ok();
            content.zeroize();
          } else {
            debug!("No more values");
            context_cloned.cancel.store(true, Ordering::Relaxed);
          }
        }
        Err(err) => {
          error!("Lock error: {}", err);
          context_cloned.cancel.store(true, Ordering::Relaxed);
        }
      }
    }
    Event::Cancelled => {
      debug!("Event cancel: Lost ownership");
      context_cloned.cancel.store(true, Ordering::Relaxed)
    }
    _ => (),
  });
  for &mime_type in TEXT_MIMES {
    data_source.offer(mime_type.to_string());
  }
  for seat in seats.borrow_mut().iter_mut() {
    let device = clipboard_manager.get_data_device(seat);
    device.quick_assign(|_, _, _| {});
    device.set_selection(Some(&data_source));
    devices.push(device);
  }
  debug!("Start event loop");
  context.open.store(true, Ordering::Relaxed);
  while !context.cancel.load(Ordering::Relaxed) {
    queue.dispatch(&mut (), |_, _, _| {})?;
  }
  context.open.store(false, Ordering::Relaxed);
  debug!("End event loop");
  for device in devices {
    device.destroy();
  }
  data_source.destroy();
  for seat in seats.borrow_mut().iter_mut() {
    seat.detach();
  }
  display.detach();
  Ok(())
}