// https://specifications.freedesktop.org/idle-inhibit-spec/latest
// https://invent.kde.org/plasma/kscreenlocker/-/blob/master/dbus/org.freedesktop.ScreenSaver.xml

use futures_lite::StreamExt;
use std::sync::{
    Arc, Mutex,
    atomic::{AtomicU32, Ordering},
};

use crate::{Event, EventSender};

#[derive(Debug)]
pub struct Inhibitor {
    cookie: u32,
    application_name: String,
    reason_for_inhibit: String,
    client: zbus::names::UniqueName<'static>,
}

#[derive(Clone)]
struct Screensaver {
    inhibitors: Arc<Mutex<Vec<Inhibitor>>>,
    last_cookie: Arc<AtomicU32>,
    event_sender: EventSender,
}

#[zbus::interface(name = "org.freedesktop.ScreenSaver")]
impl Screensaver {
    fn inhibit(
        &mut self,
        application_name: String,
        reason_for_inhibit: String,
        #[zbus(header)] header: zbus::message::Header<'_>,
    ) -> u32 {
        let cookie = self.last_cookie.fetch_add(1, Ordering::Relaxed) + 1;
        if let Some(sender) = header.sender() {
            log::info!(
                "Added screensaver inhibitor for application '{}' {:?}, reason: {}, cookie: {}",
                application_name,
                sender,
                reason_for_inhibit,
                cookie
            );
            let mut inhibitors = self.inhibitors.lock().unwrap();
            if inhibitors.is_empty() {
                let _ = self.event_sender.send(Event::ScreensaverInhibit(true));
            }
            inhibitors.push(Inhibitor {
                cookie,
                application_name,
                reason_for_inhibit,
                client: sender.to_owned(),
            });
        }
        cookie
    }

    fn un_inhibit(&mut self, cookie: u32) {
        let mut inhibitors = self.inhibitors.lock().unwrap();
        if let Some(idx) = inhibitors.iter().position(|x| x.cookie == cookie) {
            let inhibitor = inhibitors.remove(idx);
            if inhibitors.is_empty() {
                let _ = self.event_sender.send(Event::ScreensaverInhibit(false));
            }
            log::info!(
                "Removed screensaver inhibitor for application '{}' {:?}, reason: {}, cookie: {}",
                inhibitor.application_name,
                inhibitor.client,
                inhibitor.reason_for_inhibit,
                inhibitor.cookie
            );
        }
    }
}

pub async fn serve(event_sender: EventSender) -> zbus::Result<()> {
    let inhibitors = Arc::new(Mutex::new(Vec::new()));

    let screensaver = Screensaver {
        inhibitors: inhibitors.clone(),
        event_sender: event_sender.clone(),
        last_cookie: Arc::new(AtomicU32::new(0)),
    };

    // Clients vary in which path they use
    let conn = zbus::connection::Builder::session()?
        .serve_at("/ScreenSaver", screensaver.clone())?
        .serve_at("/org/freedesktop/ScreenSaver", screensaver)?
        .build()
        .await?;
    conn.request_name_with_flags(
        "org.freedesktop.ScreenSaver",
        zbus::fdo::RequestNameFlags::ReplaceExisting.into(),
    )
    .await?;

    // If a client disconnects from DBus, remove any inhibitors it has added.
    let dbus = zbus::fdo::DBusProxy::new(&conn).await?;
    let mut name_owner_stream = dbus.receive_name_owner_changed().await?;
    while let Some(event) = name_owner_stream.next().await {
        let args = event.args()?;
        if args.new_owner.is_none()
            && let zbus::names::BusName::Unique(name) = args.name
        {
            let mut inhibitors = inhibitors.lock().unwrap();
            if !inhibitors.is_empty() {
                inhibitors.retain(|inhibitor| inhibitor.client != name);
                if inhibitors.is_empty() {
                    let _ = event_sender.send(Event::ScreensaverInhibit(false));
                }
            }
        }
    }

    Ok(())
}
