//! All expects in this program must be carefully chosen on purpose. The idea is that if any of
//! them fail there is no point in continuing. All of the initialization code, for example, is full
//! of `expects`, **on purpose**, because we **want** to unwind and exit when they happen

#![cfg_attr(not(test), no_main)]
#![cfg_attr(test, allow(unused))]

mod animations;
mod cli;
mod clock;
mod output_info;
mod systemd;
mod wallpaper;
mod wayland;
use common::log::{Filter, debug, error, info, trace, warn};
use rustix::{fd::OwnedFd, fs::Timespec};

use smallvec::SmallVec;
use wallpaper::WallpaperCell;

use waybackend::{objman, types::ObjectId};
use wayland::zwlr_layer_shell_v1::Layer;

use core::{
    num::NonZeroI32,
    ptr,
    sync::atomic::{AtomicBool, Ordering},
};

use animations::Animator;
use common::ipc::{
    Answer, BgInfo, ImageReq, IpcError, IpcSocket, PixelFormat, RequestRecv, RequestSend, Scale,
};
use common::mmap::MmappedStr;
use output_info::OutputInfo;

/// Custom version of waybackend's match_enum_with_interface macro that handles
/// errors gracefully instead of panicking. This prevents crashes when the compositor
/// advertises protocol enum values (e.g., DRM formats) that waybackend doesn't recognize.
macro_rules! match_enum_with_interface {
    ($handler:ident, $object:ident, $msgs:ident, $(($variant:path, $interface:ident)),*$(,)?) => {
        match $object {
            $(
                $variant => {
                    if let Err(e) = $interface::event(&mut $handler, &mut $msgs) {
                        warn!("failed to dispatch event handler: {e}");
                        continue;
                    }
                }
            )*
        }
    }
}

// We need this because this might be set by signals, so we can't keep it in the daemon
static EXIT: AtomicBool = AtomicBool::new(false);

fn exit_daemon() {
    EXIT.store(true, Ordering::Relaxed);
}

fn should_daemon_exit() -> bool {
    EXIT.load(Ordering::Relaxed)
}

extern "C" fn signal_handler(_s: libc::c_int) {
    exit_daemon();
}

struct Daemon {
    backend: waybackend::Waybackend,
    objman: objman::ObjectManager<WaylandObject>,
    registry: ObjectId,
    compositor: ObjectId,
    shm: ObjectId,
    viewporter: ObjectId,
    layer_shell: ObjectId,
    layer: Layer,
    pixel_format: PixelFormat,
    wallpapers: SmallVec<[WallpaperCell; 2]>,
    animators: Vec<Animator>,
    namespace: String,
    use_cache: bool,
    paused: bool,
    fractional_scale_manager: Option<ObjectId>,

    /// Outputs whose wallpapers are yet to be created. We only create a wallpaper after receiving
    /// the 'done' event from wl_output.
    ///
    /// Note that, because we bind every output after binding the shm, by the time we begin
    /// receiving `wl_output::done` events, we will already now the correct shm format to use
    pending_outputs: Vec<OutputInfo>,

    /// We use PollTime as a way of making sure we draw at the right time.
    /// when we call `Daemon::draw` before the frame callback returned, we need to *not* draw and
    /// instead wait for the next callback, which we do with a short poll time.
    poll_time: Option<Timespec>,
}

impl Daemon {
    fn new(
        backend: waybackend::Waybackend,
        objman: objman::ObjectManager<WaylandObject>,
        args: cli::Cli,
        mut pending_outputs: Vec<OutputInfo>,
    ) -> Self {
        let registry = objman.get_first(WaylandObject::Registry).unwrap();
        let compositor = objman.get_first(WaylandObject::Compositor).unwrap();
        let shm = objman.get_first(WaylandObject::Shm).unwrap();
        let layer_shell = objman.get_first(WaylandObject::LayerShell).unwrap();
        let viewporter = objman.get_first(WaylandObject::Viewporter).unwrap();
        let fractional_scale_manager = objman.get_first(WaylandObject::FractionalScaler);

        pending_outputs.shrink_to_fit();

        Self {
            backend,
            objman,
            registry,
            compositor,
            shm,
            viewporter,
            layer_shell,
            layer: args.layer,
            pixel_format: args.format.unwrap_or(PixelFormat::Argb),
            wallpapers: SmallVec::new(),
            animators: Vec::with_capacity(1),
            namespace: args.namespace,
            use_cache: !args.no_cache,
            paused: false,
            fractional_scale_manager,
            pending_outputs,
            poll_time: None,
        }
    }

    /// always sets the poll time to the smalest value
    fn set_poll_time(&mut self, new_time: Timespec) {
        match self.poll_time {
            None => self.poll_time = Some(new_time),
            Some(t1) => {
                if new_time < t1 {
                    self.poll_time = Some(new_time);
                }
            }
        }
    }

    fn recv_socket_msg(&mut self, stream: IpcSocket) {
        let bytes = match stream.recv() {
            Ok(bytes) => bytes,
            Err(e) => {
                error!("FATAL: cannot read socket: {e}. Exiting...");
                exit_daemon();
                return;
            }
        };
        let request = RequestRecv::receive(bytes);
        let answer = match request {
            RequestRecv::Clear(clear) => {
                let wallpapers = self.find_wallpapers_by_names(&clear.outputs);
                self.stop_animations(&wallpapers);
                for wallpaper in &wallpapers {
                    let mut wallpaper = wallpaper.borrow_mut();
                    wallpaper.set_img_info(common::ipc::BgImg::Color(clear.color));
                    wallpaper.clear(
                        &mut self.backend,
                        &mut self.objman,
                        self.pixel_format,
                        clear.color,
                    );
                }
                crate::wallpaper::attach_buffers_and_damage_surfaces(
                    &mut self.backend,
                    &mut self.objman,
                    &wallpapers,
                );
                crate::wallpaper::commit_wallpapers(&mut self.backend, &wallpapers);
                Answer::Ok
            }
            RequestRecv::Ping => {
                Answer::Ping(self.wallpapers.iter().all(|w| w.borrow().configured))
            }
            RequestRecv::Toggle => {
                self.paused = !self.paused;
                Answer::Ok
            }
            RequestRecv::Pause => {
                self.paused = true;
                Answer::Ok
            }
            RequestRecv::Unpause => {
                self.paused = false;
                Answer::Ok
            }
            RequestRecv::Kill => {
                exit_daemon();
                Answer::Ok
            }
            RequestRecv::Query => Answer::Info(self.wallpapers_info()),
            RequestRecv::Img(ImageReq {
                transition,
                mut imgs,
                mut outputs,
                mut animations,
            }) => {
                while !imgs.is_empty() && !outputs.is_empty() {
                    let names = outputs.pop().unwrap();
                    let img = imgs.pop().unwrap();
                    let animation = if let Some(ref mut animations) = animations {
                        animations.pop()
                    } else {
                        None
                    };
                    let wallpapers = self.find_wallpapers_by_names(&names);
                    self.stop_animations(&wallpapers);
                    if let Some(mut animator) =
                        Animator::new(wallpapers, &transition, img, animation)
                    {
                        animator.frame(&mut self.backend, &mut self.objman, self.pixel_format);
                        self.animators.push(animator);
                    }
                }
                self.set_poll_time(Timespec {
                    tv_sec: 0,
                    tv_nsec: 0,
                });
                Answer::Ok
            }
        };
        if let Err(e) = answer.send(&stream) {
            error!("error sending answer to client: {e}");
        }
    }

    fn wallpapers_info(&self) -> Box<[BgInfo]> {
        self.wallpapers
            .iter()
            .map(|wallpaper| wallpaper.borrow().get_bg_info(self.pixel_format))
            .collect()
    }

    fn find_wallpapers_by_names(&self, names: &[MmappedStr]) -> SmallVec<[WallpaperCell; 2]> {
        self.wallpapers
            .iter()
            .filter_map(|wallpaper| {
                if names.is_empty() || names.iter().any(|n| wallpaper.borrow().has_name(n.str())) {
                    return Some(wallpaper.clone());
                }
                None
            })
            .collect()
    }

    fn draw(&mut self) {
        const THRESHOLD: Timespec = Timespec {
            tv_sec: 0,
            tv_nsec: 1_000_000,
        };
        // If the wallpaper is fully covered, we may run into a situation where the compositor never
        // sends us the frame event, and we always end up setting the timer to 0, thus resulting in
        // a busy-loop. This is here to ensure some throtling in that specific case. Ideally, we
        // would like for the specific value to be either configurable or match the display's
        // refresh rate. In practice 10ms works well enough
        const FPS_LIMIT: Timespec = Timespec {
            tv_sec: 0,
            tv_nsec: 10_000_000,
        };
        self.poll_time = None;

        let mut i = 0;
        while i < self.animators.len() {
            let animator = &mut self.animators[i];
            if animator
                .wallpapers
                .iter()
                .all(|w| w.borrow().is_draw_ready())
            {
                let time = animator.time_to_draw();
                if time > THRESHOLD {
                    self.set_poll_time(time);
                    i += 1;
                    continue;
                }

                if !(time.tv_sec == 0 && time.tv_nsec == 0) {
                    sleep(time);
                }

                wallpaper::attach_buffers_and_damage_surfaces(
                    &mut self.backend,
                    &mut self.objman,
                    &animator.wallpapers,
                );

                wallpaper::commit_wallpapers(&mut self.backend, &animator.wallpapers);
                animator.updt_time();
                if animator.frame(&mut self.backend, &mut self.objman, self.pixel_format) {
                    self.animators.swap_remove(i);
                    continue;
                }
            }
            let time = animator.time_to_draw();
            if time < FPS_LIMIT {
                self.set_poll_time(FPS_LIMIT);
            } else {
                self.set_poll_time(time);
            }
            i += 1;
        }
    }

    fn commit_pending_surface_changes(&mut self) {
        let mut to_stop = SmallVec::<[WallpaperCell; 2]>::with_capacity(self.wallpapers.len());
        for wallpaper in &self.wallpapers {
            if wallpaper.borrow_mut().commit_surface_changes(
                &mut self.backend,
                &self.namespace,
                self.use_cache,
            ) {
                to_stop.push(wallpaper.clone());
            }
        }
        self.stop_animations(&to_stop);
    }

    fn stop_animations(&mut self, wallpapers: &[WallpaperCell]) {
        for animator in &mut self.animators {
            animator
                .wallpapers
                .retain(|w1| !wallpapers.iter().any(|w2| w1 == w2));
        }
        self.animators.retain(|a| !a.wallpapers.is_empty());
    }
}

impl wayland::wl_display::EvHandler for Daemon {
    fn delete_id(&mut self, _: ObjectId, id: u32) {
        let removed = self.objman.remove(id);
        trace!("Removing object {id}: {removed:?}");
    }

    fn error(&mut self, _: ObjectId, object_id: ObjectId, code: u32, message: &str) {
        error!("WAYLAND PROTOCOL ERROR: object: {object_id}, code: {code}, message: {message}");
        exit_daemon();
    }
}

impl wayland::wl_registry::EvHandler for Daemon {
    fn global(&mut self, _: ObjectId, name: u32, interface: &str, version: u32) {
        if interface == "wl_output" {
            if version < 4 {
                error!("your compositor must support at least version 4 of wl_output");
            } else {
                self.pending_outputs.push(OutputInfo::new(
                    &mut self.backend,
                    &mut self.objman,
                    self.registry,
                    name,
                ));
            }
        }
    }

    fn global_remove(&mut self, _: ObjectId, name: u32) {
        if let Some(i) = self
            .wallpapers
            .iter()
            .position(|w| w.borrow().has_output_name(name))
        {
            let w = self.wallpapers.remove(i);
            w.borrow_mut().destroy(&mut self.backend);
            self.stop_animations(core::slice::from_ref(&w));
        } else if let Some(i) = self
            .pending_outputs
            .iter()
            .position(|w| w.output_name == name)
        {
            let o = self.pending_outputs.swap_remove(i);
            wayland::wl_output::req::release(&mut self.backend, o.output).unwrap();
        }
    }
}

impl wayland::wl_shm::EvHandler for Daemon {
    fn format(&mut self, _: ObjectId, format: wayland::wl_shm::Format) {
        use wayland::wl_shm::Format;
        // note: we do not set it to the most efficient format automatically because some
        // compositors kind of fuck it up. At worse it can slant the wallpaper in such a way that
        // it would cause a compositor crash.
        match format {
            Format::argb8888 => debug!("available shm format: Argb"),
            Format::abgr8888 => {
                debug!("available shm format: Xbgr");
                //if !self.forced_shm_format && self.pixel_format == PixelFormat::Argb {
                //    self.pixel_format = PixelFormat::Abgr;
                //}
            }
            Format::rgb888 => {
                debug!("available shm format: Rbg");
                //if !self.forced_shm_format && self.pixel_format != PixelFormat::Bgr {
                //    self.pixel_format = PixelFormat::Rgb
                //}
            }
            Format::bgr888 => {
                debug!("available shm format: Bgr");
                //if !self.forced_shm_format {
                //    self.pixel_format = PixelFormat::Bgr
                //}
            }
            _ => (),
        }
    }
}

impl wayland::wl_output::EvHandler for Daemon {
    fn geometry(
        &mut self,
        _sender_id: ObjectId,
        _x: i32,
        _y: i32,
        _physical_width: i32,
        _physical_height: i32,
        _subpixel: wayland::wl_output::Subpixel,
        _make: &str,
        _model: &str,
        _transform: wayland::wl_output::Transform,
    ) {
        // no-op
    }

    fn mode(
        &mut self,
        _sender_id: ObjectId,
        _flags: wayland::wl_output::Mode,
        _width: i32,
        _height: i32,
        _refresh: i32,
    ) {
        // no-op
    }

    fn done(&mut self, sender_id: ObjectId) {
        if let Some(i) = self
            .pending_outputs
            .iter()
            .position(|o| o.output == sender_id)
        {
            let output_info = self.pending_outputs.swap_remove(i);
            let wallpaper = WallpaperCell::new(self, output_info);
            self.wallpapers.push(wallpaper);
        }
    }

    fn scale(&mut self, sender_id: ObjectId, factor: i32) {
        let scale = match NonZeroI32::new(factor) {
            Some(factor) => Scale::Output(factor),
            None => {
                error!("received scale factor of 0 from compositor");
                return;
            }
        };

        for info in &mut self.pending_outputs {
            if info.output == sender_id {
                info.scale_factor = scale;
                return;
            }
        }

        for wallpaper in &self.wallpapers {
            let mut wallpaper = wallpaper.borrow_mut();
            if wallpaper.has_output(sender_id) {
                wallpaper.set_scale(scale);
                return;
            }
        }
    }

    fn name(&mut self, sender_id: ObjectId, name: &str) {
        // According to the protocol:
        // 'names are sent once per output object, and the name does not change over the
        // lifetime of the wl_output global'. So we need only set the name for the pending
        // outputs.
        for info in &mut self.pending_outputs {
            if info.output == sender_id {
                info.name = Some(name.into());
                return;
            }
        }
    }

    fn description(&mut self, sender_id: ObjectId, description: &str) {
        // unlike the `name` event, the `descriptor` event can be sent multiple times, whenever the
        // description changes, so we must have two for loops here
        for info in &mut self.pending_outputs {
            if info.output == sender_id {
                info.desc = Some(description.into());
                return;
            }
        }
        for wallpaper in &self.wallpapers {
            let mut wallpaper = wallpaper.borrow_mut();
            if wallpaper.has_output(sender_id) {
                wallpaper.set_desc(description.into());
                return;
            }
        }
    }
}

impl wayland::wl_surface::EvHandler for Daemon {
    fn enter(&mut self, _sender_id: ObjectId, output: ObjectId) {
        debug!("Output {}: Surface Enter", output.get());
    }

    fn leave(&mut self, _sender_id: ObjectId, output: ObjectId) {
        debug!("Output {}: Surface Leave", output.get());
    }

    fn preferred_buffer_scale(&mut self, _sender_id: ObjectId, _factor: i32) {
        // No-op
    }

    fn preferred_buffer_transform(
        &mut self,
        _sender_id: ObjectId,
        _transform: wayland::wl_output::Transform,
    ) {
        // No-op
    }
}

impl wayland::wl_region::EvHandler for Daemon {}

impl wayland::wl_buffer::EvHandler for Daemon {
    fn release(&mut self, sender_id: ObjectId) {
        trace!("Releasing buffer {sender_id}");
        for wallpaper in &self.wallpapers {
            if wallpaper
                .borrow_mut()
                .try_set_buffer_release_flag(&mut self.backend, sender_id)
            {
                return;
            }
        }
        warn!("We failed to find wayland buffer with id: {sender_id}. This should be impossible.");
    }
}

impl wayland::wl_callback::EvHandler for Daemon {
    fn done(&mut self, sender_id: ObjectId, _callback_data: u32) {
        for wallpaper in &self.wallpapers {
            if wallpaper.borrow().has_callback(sender_id) {
                wallpaper.borrow_mut().frame_callback_completed();
                break;
            }
        }
    }
}

impl wayland::wl_compositor::EvHandler for Daemon {}
impl wayland::wl_shm_pool::EvHandler for Daemon {}

impl wayland::zwlr_layer_shell_v1::EvHandler for Daemon {}
impl wayland::zwlr_layer_surface_v1::EvHandler for Daemon {
    fn configure(&mut self, sender_id: ObjectId, serial: u32, width: u32, height: u32) {
        for wallpaper in &mut self.wallpapers {
            if wallpaper.borrow().has_layer_surface(sender_id) {
                wallpaper
                    .borrow_mut()
                    .set_dimensions(width as i32, height as i32);
                wallpaper.borrow_mut().set_ack_serial(serial);
                break;
            }
        }
    }

    fn closed(&mut self, sender_id: ObjectId) {
        if let Some(i) = self
            .wallpapers
            .iter()
            .position(|w| w.borrow().has_layer_surface(sender_id))
        {
            let w = self.wallpapers.remove(i);
            w.borrow_mut().destroy(&mut self.backend);
            self.stop_animations(core::slice::from_ref(&w));
        }
    }
}

impl wayland::wp_fractional_scale_v1::EvHandler for Daemon {
    fn preferred_scale(&mut self, sender_id: ObjectId, scale: u32) {
        for wallpaper in &self.wallpapers {
            if wallpaper.borrow().has_fractional_scale(sender_id) {
                match NonZeroI32::new(scale as i32) {
                    Some(factor) => {
                        wallpaper.borrow_mut().set_scale(Scale::Fractional(factor));
                    }
                    None => error!("received scale factor of 0 from compositor"),
                }
                break;
            }
        }
    }
}

impl wayland::wp_viewporter::EvHandler for Daemon {}
impl wayland::wp_viewport::EvHandler for Daemon {}
impl wayland::wp_fractional_scale_manager_v1::EvHandler for Daemon {}

#[derive(Clone, Copy, Debug, PartialEq)]
enum WaylandObject {
    // standard stuff
    Display,
    Registry,
    Callback,
    Compositor,
    Shm,
    ShmPool,
    Buffer,
    Surface,
    Region,
    Output,

    // layer shell
    LayerShell,
    LayerSurface,

    // Viewporter
    Viewporter,
    Viewport,

    // Fractional Scaling
    FractionalScaler,
    FractionalScale,
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[unsafe(no_mangle)]
#[cfg(not(test))]
pub extern "C" fn main(
    argc: core::ffi::c_long,
    argv: *const *const core::ffi::c_char,
) -> core::ffi::c_long {
    // first, get the command line arguments and make the logger
    let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) };
    let cli = match cli::Cli::new(args) {
        Ok(Some(cli)) => cli,
        Ok(None) => return 0,
        Err(e) => {
            let stderr = unsafe { rustix::stdio::stderr() };
            let msg = e.to_string();
            let bufs = [
                rustix::io::IoSlice::new(msg.as_bytes()),
                rustix::io::IoSlice::new(b"\n"),
            ];
            _ = rustix::io::writev(stderr, &bufs);
            return -1;
        }
    };

    #[cfg(not(debug_assertions))]
    common::log::init(if cli.quiet {
        Filter::Error
    } else {
        Filter::Info
    });

    #[cfg(debug_assertions)]
    common::log::init(if cli.quiet {
        Filter::Debug
    } else {
        Filter::Trace
    });

    // next, initialize all wayland stuff
    let (mut backend, mut objman, mut receiver) = wayland::connect();
    let registry = objman.create(WaylandObject::Registry);
    let callback = objman.create(WaylandObject::Callback);
    let mut pending_outputs = Vec::new();
    if let Err(e) = waybackend::roundtrip(
        &mut backend,
        &mut receiver,
        registry,
        callback,
        |backend, global| {
            use WaylandObject::*;
            use wayland::*;

            waybackend::bind_globals!(
                backend,
                objman,
                registry,
                global,
                |backend, objman, global: waybackend::Global| if global.interface()
                    == wayland::wl_output::NAME
                {
                    pending_outputs.push(OutputInfo::new(backend, objman, registry, global.name()));
                },
                (wl_compositor, Compositor),
                (wl_shm, Shm),
                (zwlr_layer_shell_v1, LayerShell),
                (wp_viewporter, Viewporter),
                (wp_fractional_scale_manager_v1, FractionalScaler),
            );
        },
    ) {
        // use panic here to force Display formatting, instead of Debug
        // it both looks nicer and uses less code in the final binary
        panic!("Roundtrip failed: {e}");
    }

    // create the socket listener and setup the signal handlers
    // this will also return an error if there is an `awww-daemon` instance already
    // running
    let listener = SocketWrapper::new(&cli.namespace).unwrap();
    setup_signals();

    // use the initializer to create the Daemon, then drop it to free up the memory
    let mut daemon = Daemon::new(backend, objman, cli, pending_outputs);

    if let Err(e) = systemd::notify() {
        error!("Error sending status update to systemd: {e}");
    }

    // main loop
    while !should_daemon_exit() {
        use WaylandObject::*;
        use rustix::event::{PollFd, PollFlags};
        use wayland::*;

        daemon.backend.flush().unwrap();

        let mut fds = [
            PollFd::new(&daemon.backend.wayland_fd, PollFlags::IN),
            PollFd::new(&listener.fd, PollFlags::IN),
        ];

        // Note: we cannot use rustix::io::retry_on_intr because it makes CTRL-C fail on the
        // terminal
        match rustix::event::poll(&mut fds, daemon.poll_time.as_ref()) {
            Ok(_) => (),
            Err(rustix::io::Errno::INTR | rustix::io::Errno::WOULDBLOCK) => continue,
            Err(e) => panic!("{e}"),
        }

        clock::reset();

        let wayland_event = !fds[0].revents().is_empty();
        let socket_event = !fds[1].revents().is_empty();

        if wayland_event {
            let mut msgs = receiver.recv(&daemon.backend.wayland_fd).unwrap();
            while let Some(sender_id) = msgs.next() {
                let sender_id = match sender_id {
                    Ok(sender_id) => sender_id,
                    Err(_) => {
                        warn!(
                            "received an event for a null object from the server. This should be impossible."
                        );
                        continue;
                    }
                };
                let sender = match daemon.objman.get(sender_id) {
                    Some(obj) => obj,
                    None => {
                        warn!("received an event for an unknown object");
                        continue;
                    }
                };

                // Use our custom macro that handles errors gracefully instead of panicking.
                // This prevents crashes when the compositor advertises protocol enum values
                // (e.g., DRM formats) that waybackend doesn't recognize.
                match_enum_with_interface!(
                    daemon,
                    sender,
                    msgs,
                    (Display, wl_display),
                    (Registry, wl_registry),
                    (Callback, wl_callback),
                    (Compositor, wl_compositor),
                    (Shm, wl_shm),
                    (ShmPool, wl_shm_pool),
                    (Buffer, wl_buffer),
                    (Surface, wl_surface),
                    (Region, wl_region),
                    (Output, wl_output),
                    (LayerShell, zwlr_layer_shell_v1),
                    (LayerSurface, zwlr_layer_surface_v1),
                    (Viewporter, wp_viewporter),
                    (Viewport, wp_viewport),
                    (FractionalScaler, wp_fractional_scale_manager_v1),
                    (FractionalScale, wp_fractional_scale_v1),
                );
            }
            daemon.commit_pending_surface_changes();
        }

        if socket_event {
            // See above note about rustix::retry_on_intr
            match rustix::net::accept(&listener.fd) {
                Ok(stream) => daemon.recv_socket_msg(IpcSocket::new(stream)),
                Err(rustix::io::Errno::INTR | rustix::io::Errno::WOULDBLOCK) => continue,
                Err(e) => panic!("{e}"),
            }
        }

        if daemon.poll_time.is_some() && !daemon.paused {
            daemon.draw();
        }
    }

    drop(daemon);
    drop(listener);
    info!("Goodbye!");
    0
}

fn setup_signals() {
    // C data structure, expected to be zeroed out.
    let mut sigaction: libc::sigaction = unsafe { core::mem::zeroed() };
    unsafe { libc::sigemptyset(ptr::addr_of_mut!(sigaction.sa_mask)) };

    #[cfg(not(target_os = "aix"))]
    {
        sigaction.sa_sigaction = signal_handler as *const () as usize;
    }
    #[cfg(target_os = "aix")]
    {
        sigaction.sa_union.__su_sigaction = handler;
    }

    for signal in [libc::SIGINT, libc::SIGQUIT, libc::SIGTERM, libc::SIGHUP] {
        let ret = unsafe { libc::sigaction(signal, ptr::addr_of!(sigaction), ptr::null_mut()) };
        if ret != 0 {
            error!("Failed to install signal handler!");
        }
    }

    #[cfg(not(target_os = "aix"))]
    {
        sigaction.sa_sigaction = libc::SIG_IGN;
    }
    #[cfg(target_os = "aix")]
    {
        sigaction.sa_union.__su_sigaction = libc::SIG_IGN;
    }

    let signal = libc::SIGCHLD;
    let ret = unsafe { libc::sigaction(signal, ptr::addr_of!(sigaction), ptr::null_mut()) };
    if ret != 0 {
        error!("Failed to install signal handler!");
    }

    debug!("Finished setting up signal handlers");
}

/// This is a wrapper that makes sure to delete the socket when it is dropped
struct SocketWrapper {
    fd: OwnedFd,
    namespace: String,
}
impl SocketWrapper {
    fn new(namespace: &str) -> Result<Self, String> {
        use rustix::fs;
        let addr = IpcSocket::path(namespace);

        if fs::access(&addr, fs::Access::EXISTS).is_ok() {
            if is_daemon_running(namespace).map_err(|s| s.to_string())? {
                return Err(
                    "There is an awww-daemon instance already running on this socket!".to_string(),
                );
            }
            warn!(
                "socket file {} was not deleted when the previous daemon exited",
                addr.display()
            );
            if let Err(e) = fs::unlink(&addr) {
                return Err(format!("failed to delete previous socket: {e}"));
            }
        }

        let runtime_dir = match addr.parent() {
            Some(path) => path,
            None => return Err("couldn't find a valid runtime directory".to_owned()),
        };

        if fs::access(&runtime_dir, fs::Access::EXISTS).is_err() {
            match fs::mkdir(runtime_dir, fs::Mode::RUSR.union(fs::Mode::WUSR)) {
                Ok(()) => (),
                Err(e) => return Err(format!("failed to create runtime dir: {e}")),
            }
        }

        let socket = IpcSocket::server(namespace).map_err(|err| err.to_string())?;

        debug!("Created socket at {}", addr.display());
        Ok(Self {
            fd: socket.to_fd(),
            namespace: namespace.to_string(),
        })
    }
}

impl Drop for SocketWrapper {
    fn drop(&mut self) {
        let addr = IpcSocket::path(&self.namespace);
        if let Err(e) = rustix::fs::unlink(&addr) {
            error!("Failed to remove socket at {}: {e}", addr.display());
        }
        info!("Removed socket at {}", addr.display());
    }
}

pub fn is_daemon_running(namespace: &str) -> Result<bool, IpcError> {
    let sock = match IpcSocket::client(namespace) {
        Ok(s) => s,
        // likely a connection refused; either way, this is a reliable signal there's no surviving
        // daemon.
        Err(_) => return Ok(false),
    };

    RequestSend::Ping.send(&sock)?;
    let answer = Answer::receive(sock.recv()?);
    match answer {
        Answer::Ping(_) => Ok(true),
        _ => panic!("Daemon did not return Answer::Ping, as expected"),
    }
}

/// This will sleep for an amount of time we can roughly expected the OS to still be precise enough
/// for frame timing (125 us, currently -- copy-pasted from the `spin_sleep` crate on crates.io).
fn sleep(duration: Timespec) {
    const ACCURACY: Timespec = Timespec {
        tv_sec: 0,
        tv_nsec: 125_000,
    };

    if duration > ACCURACY {
        let d = duration - ACCURACY;
        _ = rustix::thread::nanosleep(&d);
    }
}
