use std::cell::OnceCell;
use std::cmp::min;
use std::ffi::OsString;
use std::fs::{remove_file, File, OpenOptions};
use std::io::Write;
use std::os::unix::io::OwnedFd;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::thread;

use anyhow::{bail, Context};
use calloop::futures::{executor, Scheduler};
use calloop::LoopHandle;
use calloop_wayland_source::WaylandSource;
use clap::Parser;
use cli::{Cli, RequestedTarget};
use futures_channel::oneshot::{self, Canceled};

use log::{debug, error, info, trace, warn};
use paths::screenshot_filepath;
use rustix::fs::{fcntl_getfl, fcntl_setfl, OFlags};
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
use smithay_client_toolkit::reexports::calloop::EventLoop;
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
use smithay_client_toolkit::seat::keyboard::{KeyboardData, KeyboardHandler, Keysym};
use smithay_client_toolkit::{
    delegate_keyboard, delegate_output, delegate_registry, registry_handlers,
};
use wayland_client::delegate_noop;
use wayland_client::event_created_child;
use wayland_client::globals::{registry_queue_init, GlobalList};
use wayland_client::protocol::{
    wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_pointer,
};
use wayland_client::Proxy;
use wayland_client::{
    protocol::{
        wl_buffer, wl_compositor, wl_keyboard, wl_output, wl_seat,
        wl_shm::{self, Format},
        wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface,
    },
    Connection, Dispatch, QueueHandle, WEnum,
};
use wayland_protocols::wp::fractional_scale::v1::client::{
    wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
};
use wayland_protocols::wp::single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1;
use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter};
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1;
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
use wayland_protocols_wlr::screencopy::v1::client::{
    zwlr_screencopy_frame_v1::{self, ZwlrScreencopyFrameV1},
    zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
};

use crate::paths::screenshots_directory;
use crate::slurp::pick_a_region;
use crate::sway::get_active_window_geom;
use crate::sway::get_focused_output_rect;

mod buffers;
mod cli;
mod encoder;
mod paths;
mod slurp;
mod sway;

const BORDER_WIDTH: u8 = 2;
// RGB with pre-multiplied alpha.
const BORDER_COLOUR: (u32, u32, u32) = (122 << 24, 162 << 24, 247 << 24);

/// The target we want to screenshot.
///
/// For the moment, windows are converted to the region they occupy. This will change in future
/// when we have a protocol to request a capture of a given window.
enum DesiredTarget {
    /// The entirety of the currently active output.
    Output,
    /// A custom region.
    Region {
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    },
}

/// Related to types used to detect currently active output.
struct OutputDetector;
struct OutputDetectorSurface(DesiredTarget);
/// Related to types associated with the thumbnail window.
struct Window;
#[derive(Default)]
struct Screenshot;

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    let log_level = cli.verbose.unwrap_or(log::Level::Warn);
    simple_logger::init_with_level(log_level).expect("logger configuration is valid");

    if cli.print_dir {
        let dir = screenshots_directory()?;
        println!("{}", dir.to_string_lossy());
        return Ok(());
    }

    let target = match cli
        .capture
        .expect("target must be set unless --print-dir was specified")
    {
        RequestedTarget::Window => {
            let output_rect =
                get_focused_output_rect().context("error getting region for current output")?;
            log::debug!("Output focused rect {}", output_rect);

            let region = get_active_window_geom()
                .context("error getting current window geometry via swaymsg")?;
            log::debug!("Window matches region: {}", region);

            DesiredTarget::Region {
                x: i32::from(region.x - output_rect.x),
                y: i32::from(region.y - output_rect.y),
                width: i32::from(region.width),
                height: i32::from(region.height),
            }
        }
        RequestedTarget::Output => DesiredTarget::Output,
        RequestedTarget::Region => {
            let region = pick_a_region().context("error picking a region with slurp")?;

            log::debug!("Manually selected region: {}", region);
            if region.width == 0 || region.height == 0 {
                bail!("Empty region selected; nothing to capture");
            }

            DesiredTarget::Region {
                x: i32::from(region.x),
                y: i32::from(region.y),
                width: i32::from(region.width),
                height: i32::from(region.height),
            }
        }
    };

    let conn = Connection::connect_to_env().expect("can connect via environment settings");
    let (globals, event_queue) = registry_queue_init::<State>(&conn).unwrap();
    let qh = event_queue.handle();

    // # SAFETY: these fail only if the process has reached the maximum file descriptiors.
    let (pipe_executor, pipe_scheduler) = executor().expect("initialise calloop scheduler");

    let mut event_loop = EventLoop::<State>::try_new().expect("initialize the event loop");
    let loop_signal = event_loop.get_signal();
    let handle = event_loop.handle();

    let mut state = State::new(
        target,
        cli.anchor,
        &qh,
        &globals,
        pipe_scheduler,
        cli.image_editor,
        handle.clone(),
    )
    .unwrap();
    state.autocopy = cli.copy;

    let wl_source = WaylandSource::new(conn, event_queue);

    handle
        .insert_source(wl_source, move |(), event_queue, state| {
            event_queue.dispatch_pending(state)
        })
        .expect("add wayland source into event loop");

    handle
        .insert_source(pipe_executor, |_, (), state| {
            trace!("Async executor done.");
            state.futures.fetch_sub(1, Ordering::Release);

            if state.delete_file_on_paste {
                state.delete_file();
            }
        })
        .expect("add executor source into event loop");

    event_loop
        .run(None, &mut state, |state| {
            if state.should_stop() {
                loop_signal.stop();
                loop_signal.wakeup();
            }
        })
        .expect("start main event loop");

    // Hint: wayland-rs will run `RegistryHandler<State>::ready` after all wayland globals have
    // been registered. That's a good point to continue reading the logical flow of this code.

    // TODO: if we've deleted the file, we don't need to wait for the file thread.
    // ... maybe expose a method on state `should_terminate()`?
    Ok(())
}

/// Helper type that holds references to all Wayland globals that are used at runtime.
struct Globals {
    screencopy_manager: ZwlrScreencopyManagerV1,
    // TODO: multiseat suport. See https://todo.sr.ht/~whynothugo/shotman/14
    seat: wl_seat::WlSeat,
    data_device_manager: wl_data_device_manager::WlDataDeviceManager,
    viewporter: wp_viewporter::WpViewporter,
    single_pixel_manager: wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1,
    wl_shm: wl_shm::WlShm,
}

/// Helper to detect the currently active output.
struct Detector {
    surface: wl_surface::WlSurface,
    /// Single pixel buffer for the surface used to detect current output.
    /// This reference is only required to avoid allocating it more than once.
    buffer: wl_buffer::WlBuffer,
    layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
}

#[derive(PartialEq, Debug)]
enum WindowState {
    Focused,
    Unfocused,
    Closed,
}

// `configure` and `set_size` use u32, so stick to this.
enum Configure {
    Uninitialised,
    Configured { width: u32, height: u32 },
}

#[derive(Debug)]
enum Scale {
    Unknown,
    /// A value guessed by inspecting outputs and cross-referencing with `wl_surface::enter` events.
    Guessed(i32),
    /// A value informed by the compositor via `wl_surface::preferred_buffer_scale`.
    Explicit(i32),
    /// A value as informed by `wp_fractional_scale_v1::preferred_scale`
    /// The sent scale is the numerator of a fraction with a denominator of 120.
    Fractional(u32),
}

impl Scale {
    fn to_f64(&self) -> f64 {
        match self {
            Scale::Unknown => 1.0,
            Scale::Guessed(i) | Scale::Explicit(i) => f64::from(*i),
            Scale::Fractional(u) => f64::from(*u) / 120.0,
        }
    }
}

struct State {
    registry: RegistryState,
    globals: Globals,
    detector: Detector,
    window_surface: wl_surface::WlSurface,
    shot_surface: wl_surface::WlSurface,
    window_viewport: wp_viewport::WpViewport,
    shot_viewport: wp_viewport::WpViewport,
    /// The buffer where we save screenshot data.
    shot_buffer: OnceCell<wl_buffer::WlBuffer>,
    shot_data: ScreenshotMetadata,
    shot_memfile: Option<Arc<OwnedFd>>,
    shot_filepath: FileState,
    png: PngState,
    window_layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
    screencopy_frame: Option<ZwlrScreencopyFrameV1>,
    wl_shm_formats: Vec<Format>,
    output: OutputState,
    shot_subsurface: wl_subsurface::WlSubsurface,
    ctrl: bool,
    /// This is set to true when the image is cut. The file is only deleted after pasting (e.g.: if
    /// you cut and never paste, the image remains untouched).
    delete_file_on_paste: bool,
    /// The currently taken selection (if we own it).
    current_selection: Option<wl_data_source::WlDataSource>,
    /// A counter of futures still running.
    ///
    /// These futures need to be allowed to complete before existing (e.g.:
    /// writing to file; writing to a clipboard file descriptor).
    futures: AtomicU8,
    pipe_scheduler: Scheduler<Result<(), Canceled>>,
    window_anchor: zwlr_layer_surface_v1::Anchor,
    current_output: Option<wl_output::WlOutput>,
    /// Whether to automatically copy to clipboard.
    autocopy: bool,
    window: WindowState,
    configure: Configure,
    image_editor: OsString,
    scale: Scale,
    loop_handle: LoopHandle<'static, State>,
}

/// Information on the buffer for the screenshot.
/// As reported by the compositor.
#[derive(Clone, Debug)]
pub struct BufferInfo {
    format: Format,
    height: u32,
    width: u32,
    stride: u32,
}

#[derive(Default, Clone, Debug)]
pub struct ScreenshotMetadata {
    buffer_info: Option<BufferInfo>,
    y_inverted: bool, // TODO: No idea how to support this yet.
}

delegate_keyboard!(State);
delegate_registry!(State);
delegate_output!(State);

// TODO: this should only be calculated when compositor constraints change.
// Perhaps I should set a "dirty" flag to the state when I need to redraw (or to trigger the
// first).
// This helps check if all conditions are met to begin rendering.
struct CalculatedDimensions {
    window_width: u32,
    window_height: u32,
    thumb_width: u32,
    thumb_height: u32,
}

/// The state of the screenshot file that we save to disk.
enum FileState {
    /// File has been created (writing may still be in progress).
    Created(PathBuf),
    /// File has been deleted from disk.
    Deleted,
}

/// The state of encoding the screenshot into a PNG.
///
/// Once encoding is completed, the encoded image is held in-memory.
enum PngState {
    /// Encoding has not yet completed.
    /// Has a list of file descriptors where the png needs to be written when done.
    Pending(Vec<File>),
    /// Image successfully been encoded.
    Done(Arc<Vec<u8>>),
    /// Encoding the PNG failed.
    Failed,
}

impl Default for PngState {
    fn default() -> PngState {
        PngState::Pending(Vec::new())
    }
}

impl Globals {
    fn new(qh: &QueueHandle<State>, globals: &GlobalList) -> anyhow::Result<Globals> {
        let wl_shm = globals.bind::<wl_shm::WlShm, _, _>(qh, 1..=1, ())?;
        let screencopy_manager = globals.bind::<ZwlrScreencopyManagerV1, _, _>(qh, 2..=3, ())?;
        let wp_viewporter = globals.bind::<wp_viewporter::WpViewporter, _, _>(qh, 1..=1, ())?;
        let wl_seat = globals.bind::<wl_seat::WlSeat, _, _>(qh, 1..=7, ())?;
        let wl_data_device_manager =
            globals.bind::<wl_data_device_manager::WlDataDeviceManager, _, _>(qh, 1..=3, ())?;
        let single_pixel_manager =
            globals.bind::<wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1, _, _>(
                qh,
                1..=1,
                (),
            )?;
        Ok(Globals {
            wl_shm,
            screencopy_manager,
            viewporter: wp_viewporter,
            seat: wl_seat,
            data_device_manager: wl_data_device_manager,
            single_pixel_manager,
        })
    }
}

impl State {
    fn new(
        target: DesiredTarget,
        anchor: zwlr_layer_surface_v1::Anchor,
        qh: &QueueHandle<Self>,
        global_list: &GlobalList,
        pipe_scheduler: Scheduler<Result<(), Canceled>>,
        image_editor: OsString,
        loop_handle: LoopHandle<'static, State>,
    ) -> anyhow::Result<Self> {
        let globals = Globals::new(qh, global_list)?;
        let wlr_layer_shell =
            global_list.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>(qh, 2..=4, ())?;
        let wl_subcompositor =
            global_list.bind::<wl_subcompositor::WlSubcompositor, _, _>(qh, 1..=1, ())?;
        // TODO: bump to v3 for linux_dma_buffer
        let compositor: wl_compositor::WlCompositor = global_list.bind(qh, 4..=4, ())?;

        let detector_surface = compositor.create_surface(qh, OutputDetectorSurface(target));
        let window_surface = compositor.create_surface(qh, Window);
        let shot_surface = compositor.create_surface(qh, ());

        match global_list.bind::<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, _, _>(
            qh,
            1..=1,
            (),
        ) {
            Ok(fractional_scale_manager) => {
                Some(fractional_scale_manager.get_fractional_scale(&window_surface, qh, Window))
            }
            Err(e) => {
                info!("Fractional scale support disabled: {}", e);
                None
            }
        };

        let shot_subsurface =
            wl_subcompositor.get_subsurface(&shot_surface, &window_surface, qh, ());

        let detector_layer_surface = wlr_layer_shell.get_layer_surface(
            &detector_surface,
            None,
            zwlr_layer_shell_v1::Layer::Overlay,
            String::from("shotman"),
            qh,
            OutputDetector,
        );
        let window_layer_surface = wlr_layer_shell.get_layer_surface(
            &window_surface,
            None,
            zwlr_layer_shell_v1::Layer::Overlay,
            String::from("shotman"),
            qh,
            ThumbnailWindow,
        );

        // TODO: check out how set_exclusive_zone(-1) affects phosh
        window_layer_surface.set_anchor(anchor);
        window_layer_surface
            .set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::None);

        detector_layer_surface.set_size(1, 1);
        detector_surface.commit();

        let mut outputs = Vec::new();
        let shot_filepath = {
            match screenshot_filepath() {
                Ok(filepath) => {
                    let file = OpenOptions::new()
                        .write(true)
                        .create_new(true)
                        .open(filepath.clone())
                        .expect("can create screenshot file");
                    outputs.push(file);
                    FileState::Created(filepath)
                }
                Err(e) => {
                    error!("Failed to resolve path for file. Not saving png: {}.", e);
                    FileState::Deleted
                }
            }
        };

        let window_viewport = globals.viewporter.get_viewport(&window_surface, qh, ());
        let shot_viewport = globals.viewporter.get_viewport(&shot_surface, qh, ());

        let detector_buffer =
            globals
                .single_pixel_manager
                .create_u32_rgba_buffer(0, 0, 0, 0, qh, OutputDetector);
        detector_surface.attach(Some(&detector_buffer), 0, 0);

        let detector = Detector {
            surface: detector_surface,
            buffer: detector_buffer,
            layer_surface: detector_layer_surface,
        };

        Ok(Self {
            globals,
            detector,
            registry: RegistryState::new(global_list),
            window_surface,
            shot_surface,
            window_viewport,
            shot_viewport,
            shot_buffer: OnceCell::new(),
            shot_data: ScreenshotMetadata::default(),
            shot_memfile: None,
            shot_filepath,
            png: PngState::Pending(outputs),
            window_layer_surface,
            screencopy_frame: None,
            wl_shm_formats: Vec::new(),
            output: OutputState::new(global_list, qh),
            shot_subsurface,
            ctrl: false,
            delete_file_on_paste: false,
            current_selection: None,
            futures: 0.into(),
            pipe_scheduler,
            window_anchor: anchor,
            current_output: None,
            autocopy: false,
            window: WindowState::Unfocused,
            configure: Configure::Uninitialised,
            image_editor,
            scale: Scale::Unknown,
            loop_handle,
        })
    }

    /// Indicates whether the event loop should stop.
    ///
    /// The event loop should continue as long as we are holding a selection, or rendering a
    /// surface.
    fn should_stop(&self) -> bool {
        match self.png {
            // TODO: if closed + deleted, then we should exit immediately.
            PngState::Pending(_) => false,
            PngState::Done(_) | PngState::Failed => {
                self.current_selection.is_none()
                    && self.window == WindowState::Closed
                    && self.futures.load(Ordering::Acquire) == 0
            }
        }
    }

    fn current_output_info(&self) -> anyhow::Result<OutputInfo> {
        let info = self
            .output
            .info(
                self.current_output
                    .as_ref()
                    .context("current output is undefined")?,
            )
            .context("no info for current output")?;
        Ok(info)
    }

    fn calculate_dimensions(&self) -> anyhow::Result<CalculatedDimensions> {
        let shot_info = self
            .shot_data
            .buffer_info
            .as_ref()
            .context("no buffer_info for screenshot")?;

        let output_info = self.current_output_info()?;
        let (output_width, output_height) =
            output_info.logical_size.context("unknown output size")?;

        let max_thumb_width = u32::try_from(output_width)? * 2 / 5;
        let max_thumb_height = u32::try_from(output_height)? * 2 / 5;

        // Multiply by 1000 to avoid this always rounding down to 100.
        // It's probably faster and more readable than casting everything to float and back.
        let shot_ratio = shot_info.width * 1000 / shot_info.height;
        let area_ratio = max_thumb_width * 1000 / max_thumb_height;

        let (thumb_width, thumb_height) = if area_ratio > shot_ratio {
            // window wider
            (
                shot_info.width * max_thumb_height / shot_info.height,
                max_thumb_height,
            )
        } else {
            // window taller
            (
                max_thumb_width,
                shot_info.height * max_thumb_width / shot_info.width,
            )
        };

        // TODO: Do BORDER_WIDTH references need to account for scaling?
        let (window_width, window_height) = (
            thumb_width + u32::from(BORDER_WIDTH * 2),
            thumb_height + u32::from(BORDER_WIDTH * 2),
        );

        Ok(CalculatedDimensions {
            window_width,
            window_height,
            thumb_width,
            thumb_height,
        })
    }

    /// Render the main window with the screenshot inside of it.
    /// The `max_size` parameter should be used with values from Configure events:
    ///
    /// > The size is a hint, in the sense that the client is free to ignore it if it doesn't
    /// > resize, pick a smaller size (to satisfy aspect ratio or resize in steps of N*M pixels).
    fn render_window(
        &mut self,
        qh: &QueueHandle<Self>,
        width: u32,
        height: u32,
    ) -> anyhow::Result<()> {
        match self.configure {
            Configure::Configured {
                width: new_width,
                height: new_height,
            } if (width == new_width && height == new_height) => {
                // See: https://github.com/swaywm/sway/issues/7302
                debug!("Configure size for main window has not changed.");
                return Ok(());
            }
            _ => debug!("Configuring (and rendering) main window"),
        };

        let CalculatedDimensions {
            window_width,
            window_height,
            thumb_width,
            thumb_height,
        } = self.calculate_dimensions()?;

        if window_width > width || window_height > height {
            // FIXME: need to restrict `calculate_dimensions` to the configured size.
            warn!("calculated dimensions are larger than what the compositor requested!");
        }

        self.configure = Configure::Configured {
            width: window_width,
            height: window_height,
        };

        // TODO: should fall back here and just use margins that don't rely on output size.
        let output_info = self.current_output_info()?;
        let (output_width, output_height) =
            output_info.logical_size.context("unknown output size")?;

        // Note: the compositor will multiply this by scale.
        let margin = min(output_width, output_height) / 12;
        self.window_layer_surface
            .set_margin(margin, margin, margin, margin);

        self.window_layer_surface
            .set_size(window_width, window_height);

        self.shot_subsurface
            .set_position(i32::from(BORDER_WIDTH), i32::from(BORDER_WIDTH));
        self.shot_viewport
            .set_destination(i32::try_from(thumb_width)?, i32::try_from(thumb_height)?);

        self.shot_surface.commit();
        self.window_surface.commit();

        self.draw_borders(qh)?;

        Ok(())
    }

    fn draw_borders(&self, qh: &QueueHandle<State>) -> anyhow::Result<()> {
        let (width, height) = match self.configure {
            Configure::Uninitialised => bail!("cannot render borders before first configure event"),
            Configure::Configured { width, height } => (width, height),
        };

        let (r, g, b, a) = if self.window == WindowState::Focused {
            (BORDER_COLOUR.0, BORDER_COLOUR.1, BORDER_COLOUR.2, u32::MAX)
        } else {
            (u32::MAX / 2, u32::MAX / 2, u32::MAX / 2, u32::MAX)
        };
        let window_buffer = self
            .globals
            .single_pixel_manager
            .create_u32_rgba_buffer(r, g, b, a, qh, Window);
        self.window_surface.attach(Some(&window_buffer), 0, 0);
        self.window_surface.damage_buffer(0, 0, 1, 1);
        self.window_viewport
            .set_destination(i32::try_from(width)?, i32::try_from(height)?);

        self.window_surface.commit();
        window_buffer.destroy();

        Ok(())
    }

    /// Closes the main window.
    /// Returns None if a surface was None, but this usually implies that the window is already
    /// closed.
    fn close(&mut self) {
        self.window_layer_surface.destroy();
        self.window_surface.destroy();
        self.window = WindowState::Closed;

        // TODO: Destroy a lot of other objects here.
    }

    fn paste_as_png(&mut self, fd: OwnedFd) {
        // Received file descriptors MAY be non-blocking, but that's not supported here.
        let opt = fcntl_getfl(&fd).unwrap();
        fcntl_setfl(&fd, opt & !OFlags::NONBLOCK).unwrap();

        let file = File::from(fd);
        match self.png {
            PngState::Pending(ref mut files) => {
                files.push(file);
            }
            PngState::Done(ref png) => {
                // TODO: I should be able to use `calloop::LoopHandle::adapt_io` here.
                // However, that won't work for regular files, much MUST be blocking, so I
                // need two code-paths to write the PNG to a file descriptor.
                self.write_png(png.clone(), file);
            }
            PngState::Failed => {
                error!("Encoding image into PNG failed; cannot paste.");
            }
        }
    }

    /// Copies the image data and file. It may then be pasted as an image (e.g.: in a browser or
    /// image editor), or as a file (e.g.: in a file manager).
    fn copy(&mut self, qh: &QueueHandle<State>, serial: u32) {
        self.delete_file_on_paste = false;

        debug!("Taking CLIPBOARD selection");

        let data_source = self.globals.data_device_manager.create_data_source(qh, ());
        let data_device =
            self.globals
                .data_device_manager
                .get_data_device(&self.globals.seat, qh, ());
        // TODO: it might be a good idea to pass an Arc with PNG buffer and the filename to the
        // data_source as UserData so it has its own access to that.
        data_source.offer(String::from("image/png"));
        data_device.set_selection(Some(&data_source), serial);
        self.current_selection = Some(data_source);
    }

    /// Cuts the image file. This may only be pasted as an image, and will remove the file from the
    /// default screenshots location.
    fn cut(&mut self, qh: &QueueHandle<State>, serial: u32) {
        self.copy(qh, serial);
        self.delete_file_on_paste = true;
    }

    fn edit(&mut self) -> anyhow::Result<()> {
        // FIXME: This has issues when it runs before file finishes saving (very unlikely, but possible).
        if let FileState::Created(filepath) = &self.shot_filepath {
            Command::new(&self.image_editor)
                .arg(filepath)
                .spawn()
                .context("gimp command failed to start")?;
        } else {
            anyhow::bail!("screenshot file does not exist and cannot be edited")
        }

        Ok(())
    }

    // Applies a new anchor. Note that the new anchor must be a single value/bit.
    fn apply_anchor(&mut self, anchor: zwlr_layer_surface_v1::Anchor) {
        self.window_anchor = match anchor {
            zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Right => {
                self.window_anchor
                    & (zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Bottom)
                    | anchor
            }
            zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Bottom => {
                self.window_anchor
                    & (zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Right)
                    | anchor
            }
            _ => unreachable!(),
        };
        self.window_layer_surface.set_anchor(self.window_anchor);
        self.window_surface.commit();
    }

    /// Deletes the file from disk.
    fn delete_file(&mut self) {
        // TODO: I should re-take the clipboard at this point, offering only the image data, but not the filepath.
        match &self.shot_filepath {
            FileState::Created(path) => match remove_file(path) {
                Ok(()) => {
                    self.shot_filepath = FileState::Deleted;
                    info!("File deleted");
                }
                Err(err) => match err.kind() {
                    std::io::ErrorKind::PermissionDenied => {
                        warn!("permission denied deleting screenshot");
                    }
                    _ => {
                        error!("unexpected error deleting file: {}", err);
                    }
                },
            },
            FileState::Deleted => {} // no-op; file already deleted.
        }
    }

    fn steal_focus(&self) {
        self.window_layer_surface
            .set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive);
        // TODO: commit on Frame event?
        self.window_surface.commit();
    }

    fn relinquish_focus(&self) {
        self.window_layer_surface
            .set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::None);
        // TODO: commit on Frame event?
        self.window_surface.commit();
    }

    fn encode_image_to_png(&mut self) -> anyhow::Result<()> {
        let memfile = self
            .shot_memfile
            .as_ref()
            .context("memfile shuold not be None")?
            .clone();

        let buf_info = self
            .shot_data
            .buffer_info
            .as_ref()
            .context("buf_ready shuold not be None")?
            .clone();

        {
            let (tx, rx) = oneshot::channel();
            thread::spawn(move || {
                // TODO: remove unwrap here and handle error properly (mostly just log it).
                let png = encoder::write_buffer_to_png_file(&memfile, &buf_info).unwrap();
                info!("Finished encoding into png ({} bytes).", png.len());
                tx.send(png).expect("send finish signal for thread-future");
            });

            self.futures.fetch_add(1, Ordering::Release);

            let (executor, scheduler) = executor().context("initialising encoder executor")?;
            self.loop_handle
                .insert_source(executor, image_encoded_cb)
                .expect("insert encoder executor into main loop");

            if let Err(err) = scheduler.schedule(rx) {
                self.futures.fetch_sub(1, Ordering::Release);
                bail!(err);
            };
        }
        Ok(())
    }

    fn show_screenshot_thumbnail(
        &mut self,
        frame: &zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
    ) -> anyhow::Result<()> {
        let buffer = self
            .shot_buffer
            .get()
            .context("screenshot buffer must be initialised")?;

        self.shot_surface.attach(Some(buffer), 0, 0);
        frame.destroy();

        // Done before rendering but applies during the render's commit.
        self.window_layer_surface
            .set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive);
        // Don't unwrap here, put something into the event loop.
        // Get rid of all unwraps; either raise error, or debug!().
        let CalculatedDimensions {
            window_width,
            window_height,
            ..
        } = self.calculate_dimensions()?;
        self.window_layer_surface
            .set_size(window_width, window_height);
        self.window_surface.commit();
        Ok(())
    }

    fn png_ready(&mut self, png_data: Vec<u8>) {
        let png = Arc::from(png_data);
        let new_state = PngState::Done(Arc::clone(&png));
        let old_state = std::mem::replace(&mut self.png, new_state);

        if let PngState::Pending(files) = old_state {
            for file in files {
                self.write_png(png.clone(), file); // Each future gets a clone of this Arc.
            }
        }
    }

    fn write_png(&self, data: Arc<Vec<u8>>, mut target: File) {
        self.futures.fetch_add(1, Ordering::Release);
        let (tx, rx) = oneshot::channel();

        thread::spawn(move || {
            target.write_all(&data).unwrap();
            info!("Finished writing into file descriptor.");
            tx.send(()).expect("receiver for png-write is not dropped");
        });
        self.pipe_scheduler
            .schedule(rx)
            .expect("pipe executor remains undropped");
    }
}

fn image_encoded_cb(maybe_png: Result<Vec<u8>, Canceled>, _: &mut (), state: &mut State) {
    trace!("Encoder future is done.");
    state.futures.fetch_sub(1, Ordering::Release);

    match maybe_png {
        Ok(p) => state.png_ready(p),
        Err(e) => {
            error!("Encoding the screenshot into PNG failed.");
            error!("The file will not be saved. Pasting will not work!");
            error!("Encoding error: {}", e);
            state.png = PngState::Failed;
        }
    };
}

impl ProvidesRegistryState for State {
    fn registry(&mut self) -> &mut RegistryState {
        &mut self.registry
    }

    registry_handlers!(OutputState); // Delegate management of new outputs.
}

// These interfaces have no events, so there's nothing to handle.
delegate_noop!(State: wl_compositor::WlCompositor);
delegate_noop!(State: ZwlrScreencopyManagerV1);
delegate_noop!(State: wl_subcompositor::WlSubcompositor);
delegate_noop!(State: wl_subsurface::WlSubsurface);
delegate_noop!(State: wp_viewporter::WpViewporter);
delegate_noop!(State: wp_viewport::WpViewport);
delegate_noop!(State: wl_shm_pool::WlShmPool);
delegate_noop!(State: wl_data_device_manager::WlDataDeviceManager);
delegate_noop!(State: zwlr_layer_shell_v1::ZwlrLayerShellV1);
delegate_noop!(State: wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1);
delegate_noop!(State: wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);

// Don't care about other clients offering selections, nor things being dragged.
delegate_noop!(State: ignore wl_data_offer::WlDataOffer);
// Only applies to the screenshot surface. Nothing to do.
delegate_noop!(State: ignore wl_surface::WlSurface);

impl Dispatch<wl_surface::WlSurface, Window> for State {
    fn event(
        state: &mut Self,
        _: &wl_surface::WlSurface,
        event: wl_surface::Event,
        _: &Window,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        match event {
            wl_surface::Event::Enter { output } => {
                match state.scale {
                    Scale::Unknown | Scale::Guessed(_) => {
                        let output_info = state.current_output_info().unwrap();
                        let scale = output_info.scale_factor;
                        state.scale = Scale::Guessed(scale); // FIXME: ugly
                    }
                    Scale::Explicit(_) | Scale::Fractional(_) => {}
                }
                // TODO: OutputState has a "add_scale_watcher", check that out.

                state.current_output = Some(output);
                // TODO: if this has changed, check the scale that we're applying.
            }
            wl_surface::Event::PreferredBufferScale { factor } => match state.scale {
                Scale::Unknown | Scale::Guessed(_) | Scale::Explicit(_) => {
                    state.scale = Scale::Explicit(factor);
                }
                Scale::Fractional(_) => {}
            },
            _ => (),
        };
    }
}

impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, Window> for State {
    fn event(
        state: &mut Self,
        _: &wp_fractional_scale_v1::WpFractionalScaleV1,
        event: wp_fractional_scale_v1::Event,
        _: &Window,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let wp_fractional_scale_v1::Event::PreferredScale { scale } = event {
            state.scale = Scale::Fractional(scale);
        };
    }
}

/// Simple hack to determine the "currently active output".
impl Dispatch<wl_surface::WlSurface, OutputDetectorSurface> for State {
    fn event(
        state: &mut Self,
        _: &wl_surface::WlSurface,
        event: wl_surface::Event,
        user_data: &OutputDetectorSurface,
        _: &Connection,
        qh: &QueueHandle<Self>,
    ) {
        if let wl_surface::Event::Enter { output } = event {
            match user_data.0 {
                DesiredTarget::Output => {
                    state.screencopy_frame = state
                        .globals
                        .screencopy_manager
                        .capture_output(1, &output, qh, ())
                        .into();
                }
                DesiredTarget::Region {
                    x,
                    y,
                    width,
                    height,
                } => {
                    state.screencopy_frame = state
                        .globals
                        .screencopy_manager
                        .capture_output_region(1, &output, x, y, width, height, qh, ())
                        .into();
                }
            }

            state.current_output = Some(output);

            // The detector has done its job and can now be destroyed.
            state.detector.layer_surface.destroy();
            state.detector.surface.destroy();
            state.detector.buffer.destroy();
        };
    }
}

impl Dispatch<wl_shm::WlShm, ()> for State {
    fn event(
        state: &mut Self,
        _: &wl_shm::WlShm,
        event: wl_shm::Event,
        (): &(),
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let wl_shm::Event::Format { format } = event {
            match format {
                WEnum::Value(f) => {
                    state.wl_shm_formats.push(f);
                }
                WEnum::Unknown(u) => {
                    error!("Compositor sent and unknown format: {}.", u);
                }
            }
        }
    }
}

impl Dispatch<wl_buffer::WlBuffer, Window> for State {
    fn event(
        _: &mut Self,
        buffer: &wl_buffer::WlBuffer,
        event: wl_buffer::Event,
        _: &Window,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let wl_buffer::Event::Release = event {
            debug!("Window buffer released: {}.", buffer.id());
        }
    }
}

impl Dispatch<wl_buffer::WlBuffer, Screenshot> for State {
    fn event(
        _: &mut Self,
        buffer: &wl_buffer::WlBuffer,
        event: wl_buffer::Event,
        _: &Screenshot,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let wl_buffer::Event::Release = event {
            debug!("Screnshot buffer released: {}.", buffer.id());
        }
    }
}

impl Dispatch<wl_buffer::WlBuffer, OutputDetector> for State {
    fn event(
        _: &mut Self,
        buffer: &wl_buffer::WlBuffer,
        event: wl_buffer::Event,
        _: &OutputDetector,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let wl_buffer::Event::Release = event {
            buffer.destroy();
        }
    }
}

struct ThumbnailWindow;

// TODO: user data here needs to contain width and height
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ThumbnailWindow> for State {
    fn event(
        state: &mut Self,
        wlr_layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
        event: zwlr_layer_surface_v1::Event,
        _: &ThumbnailWindow,
        _: &Connection,
        qh: &QueueHandle<Self>,
    ) {
        match event {
            zwlr_layer_surface_v1::Event::Configure {
                serial,
                width,
                height,
            } => {
                wlr_layer_surface.ack_configure(serial);
                state
                    .render_window(qh, width, height)
                    .expect("error handling configure event");
            }
            zwlr_layer_surface_v1::Event::Closed => {
                state.close();
            }
            _ => {}
        }
    }
}

/// Hack to detect currently active output.
///
/// We render a 1x1px invisible layer shell, and detect on which output the surface enters when
/// rendered. The `configure` event should only be received once, at which point we simply commit
/// the 1x1px empty surface.
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, OutputDetector> for State {
    fn event(
        state: &mut Self,
        wlr_layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
        event: zwlr_layer_surface_v1::Event,
        _: &OutputDetector,
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event {
            wlr_layer_surface.ack_configure(serial);
            state.detector.surface.commit();
        }
    }
}

impl Dispatch<wl_seat::WlSeat, ()> for State {
    fn event(
        _: &mut Self,
        seat: &wl_seat::WlSeat,
        event: wl_seat::Event,
        (): &(),
        _: &Connection,
        qh: &QueueHandle<Self>,
    ) {
        if let wl_seat::Event::Capabilities {
            capabilities: WEnum::Value(capabilities),
        } = event
        {
            if capabilities.contains(wl_seat::Capability::Keyboard) {
                seat.get_keyboard(qh, KeyboardData::new(seat.clone()));
            }
            if capabilities.contains(wl_seat::Capability::Pointer) {
                seat.get_pointer(qh, ());
            }
        }
    }
}

impl KeyboardHandler for State {
    fn enter(
        &mut self,
        _: &Connection,
        qh: &QueueHandle<Self>,
        _: &wl_keyboard::WlKeyboard,
        _: &wl_surface::WlSurface,
        serial: u32,
        _: &[u32],
        _: &[Keysym],
    ) {
        // A serial is required in order to copy into clipboard (and the client must have a
        // foreground surface). So the --copy flag is implemented when a surface gains keyboard
        // focus.
        if self.autocopy {
            self.copy(qh, serial);
            self.autocopy = false;
        }
        self.window = WindowState::Focused;
        self.draw_borders(qh).unwrap();
    }

    fn leave(
        &mut self,
        _: &Connection,
        qh: &QueueHandle<Self>,
        _: &wl_keyboard::WlKeyboard,
        _: &wl_surface::WlSurface,
        _: u32,
    ) {
        if self.window == WindowState::Focused {
            self.window = WindowState::Unfocused;
            self.draw_borders(qh).unwrap();
        }
    }

    fn press_key(
        &mut self,
        _: &Connection,
        qh: &QueueHandle<Self>,
        _: &wl_keyboard::WlKeyboard,
        serial: u32,
        event: smithay_client_toolkit::seat::keyboard::KeyEvent,
    ) {
        match event.keysym {
            Keysym::Escape | Keysym::q => {
                self.close();
            }
            Keysym::Delete | Keysym::d => {
                self.delete_file();
                self.close();
            }
            Keysym::XF86_Copy => self.copy(qh, serial),
            Keysym::XF86_Cut => self.cut(qh, serial),
            Keysym::c if self.ctrl => self.copy(qh, serial),
            Keysym::x if self.ctrl => self.cut(qh, serial),
            // Vim-like motion keys:
            Keysym::h => self.apply_anchor(zwlr_layer_surface_v1::Anchor::Left),
            Keysym::j => self.apply_anchor(zwlr_layer_surface_v1::Anchor::Bottom),
            Keysym::k => self.apply_anchor(zwlr_layer_surface_v1::Anchor::Top),
            Keysym::l => self.apply_anchor(zwlr_layer_surface_v1::Anchor::Right),
            Keysym::e => {
                self.edit().unwrap();
            }
            Keysym::f | Keysym::XF86_FullScreen => unimplemented!(),
            Keysym::space => {
                self.relinquish_focus();
            }
            _ => {}
        }
    }

    fn release_key(
        &mut self,
        _: &Connection,
        _: &QueueHandle<Self>,
        _: &wl_keyboard::WlKeyboard,
        _: u32,
        _: smithay_client_toolkit::seat::keyboard::KeyEvent,
    ) {
        // No-op
    }

    fn update_modifiers(
        &mut self,
        _: &Connection,
        _: &QueueHandle<Self>,
        _: &wl_keyboard::WlKeyboard,
        _: u32,
        event: smithay_client_toolkit::seat::keyboard::Modifiers,
    ) {
        self.ctrl = event.ctrl;
    }
}

// HACK: sway does not fully support zwlr_layer_shell v4, and does not support the "on demand"
// focus mode. This handler tries to emulate that, by requesting exclusive focus when the mouse in
// on any of our surfaces, and relinquishing focus when the mouse leaves all surfaces.
//
// This is probably quite imperfect, but mouse-driven usages it kinda secondary to this tool
// anyway.
impl Dispatch<wl_pointer::WlPointer, ()> for State {
    fn event(
        state: &mut Self,
        _: &wl_pointer::WlPointer,
        event: <wl_pointer::WlPointer as wayland_client::Proxy>::Event,
        (): &(),
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        match event {
            wl_pointer::Event::Enter { .. } => {
                debug!("Pointer entered; stealing focus");
                state.steal_focus();
            }
            wl_pointer::Event::Leave { .. } => {
                debug!("Pointer left; relinquishing focus");
                state.relinquish_focus();
            }
            // TODO: Some other events here will help with clicks and alike, but that's all
            // out-of-scope for now.
            _ => {}
        }
    }
}

impl OutputHandler for State {
    fn output_state(&mut self) -> &mut OutputState {
        &mut self.output
    }

    fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {
        // TODO: should set scale here, if applicable
    }

    fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {
        // TODO: Should update scale here, if applicable.
    }

    fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {
        // Nothing to do here.
    }
}

// TODO: Implement a wrapper with its own state, in the same style as OutputState.
// This should eventually let us drop lots of variable from `State` and tie them to the `Frame`
// instead.
impl Dispatch<ZwlrScreencopyFrameV1, ()> for State {
    fn event(
        state: &mut Self,
        frame: &zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
        event: zwlr_screencopy_frame_v1::Event,
        (): &(),
        _: &Connection,
        qh: &QueueHandle<Self>,
    ) {
        match event {
            zwlr_screencopy_frame_v1::Event::Buffer {
                format,
                width,
                height,
                stride,
            } => {
                let format = match format {
                    WEnum::Value(f) => f,
                    WEnum::Unknown(u) => {
                        panic!("Compositor yielded invalid frame format: {u}");
                    }
                };

                state.shot_data.buffer_info = BufferInfo {
                    format,
                    height,
                    width,
                    stride,
                }
                .into();

                let (buffer, fd) = buffers::empty_buffer::<_, _, Screenshot>(
                    &state.globals.wl_shm,
                    width,
                    height,
                    format,
                    qh,
                )
                .unwrap();

                state.shot_memfile = Some(Arc::new(fd));
                if state.shot_buffer.set(buffer).is_err() {
                    error!("Tried to init `shot_buffer`, but it was already initialised.");
                }
            }
            zwlr_screencopy_frame_v1::Event::Flags { flags } => {
                if flags == WEnum::Value(zwlr_screencopy_frame_v1::Flags::YInvert) {
                    state.shot_data.y_inverted = true;
                }
            }
            zwlr_screencopy_frame_v1::Event::Ready { .. } => {
                if let Err(err) = state.encode_image_to_png() {
                    error!("Error encoding image into PNG: {}", err);
                    state.png = PngState::Failed;
                };
                if let Err(err) = state.show_screenshot_thumbnail(frame) {
                    error!("Error rendering thumbnail: {}", err);
                    state.window = WindowState::Closed;
                };
            }
            zwlr_screencopy_frame_v1::Event::Failed => {
                // Example scenario: Phone running Phosh, tried to take screenshot via SSH
                // when the device was locked and the was screen off.
                error!(
                    "Failed to take screenshot. Compositor sent 'zwlr_screencopy_frame_v1::failed'"
                );
                // TODO: change exit state
            }
            zwlr_screencopy_frame_v1::Event::Damage { .. } => {
                warn!("Got damage tracking event but did not ask for it.");
                // Not applicable here, only one screencopy is executed.
            }
            zwlr_screencopy_frame_v1::Event::LinuxDmabuf { .. } => {
                debug!("linux dmabuf is not implemented");
            }
            zwlr_screencopy_frame_v1::Event::BufferDone => {
                if let Some(buf) = &state.shot_buffer.get() {
                    frame.copy(buf);
                } else {
                    unreachable!("Somehow got BufferDone before initialising buffer");
                }
            }
            e => {
                warn!("Unexpected event: {:?}", e);
            }
        }
    }
}

impl Dispatch<wl_data_device::WlDataDevice, ()> for State {
    fn event(
        _: &mut Self,
        _: &wl_data_device::WlDataDevice,
        _: <wl_data_device::WlDataDevice as wayland_client::Proxy>::Event,
        (): &(),
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        // None of these are necessary.
    }

    event_created_child!(State, wl_data_device::WlDataDevice, [
        wl_data_device::EVT_DATA_OFFER_OPCODE => (wl_data_offer::WlDataOffer, ()),
    ]);
}

impl Dispatch<wl_data_source::WlDataSource, ()> for State {
    fn event(
        state: &mut Self,
        data_source: &wl_data_source::WlDataSource,
        event: <wl_data_source::WlDataSource as wayland_client::Proxy>::Event,
        (): &(),
        _: &Connection,
        _: &QueueHandle<Self>,
    ) {
        match event {
            wl_data_source::Event::Send { mime_type, fd } => {
                if let "image/png" = mime_type.as_ref() {
                    state.paste_as_png(fd);
                } else {
                    warn!("Tried to paste as unknown mime-type: {}", mime_type);
                };
            }
            wl_data_source::Event::Cancelled => {
                if Some(data_source) == state.current_selection.as_ref() {
                    state.current_selection = None;
                };
            }
            wl_data_source::Event::Target { .. }
            | wl_data_source::Event::DndDropPerformed
            | wl_data_source::Event::DndFinished
            | wl_data_source::Event::Action { .. } => unreachable!(), // Used only for drag-and-drop
            _ => unreachable!("Received unexpected wl_data_source event"),
        }
    }
}
