use std::{
    io,
    process::{Command, ExitStatus, Stdio},
};

use thiserror::Error;

use crate::Region;

#[derive(Error, Debug)]
pub(crate) enum Error {
    #[error("error executing slurp")]
    SlurpFailed(#[from] io::Error),

    #[error("slurp's output is invalid: {0}")]
    InvalidOutput(String),

    #[error("slurp did not succeed: {0}")]
    NonSuccess(ExitStatus),
}

pub(crate) fn pick_a_region() -> Result<Region, Error> {
    let output = Command::new("slurp")
        .args(["-f", "%X,%Y,%W,%H"])
        .stdout(Stdio::piped())
        .output()?;
    if !output.status.success() {
        return Err(Error::NonSuccess(output.status));
    }

    let parts = output
        .stdout
        .split(|&b| b == b',')
        .map(parse_u16_from_ascii)
        .collect::<Option<Vec<u16>>>()
        .ok_or(Error::InvalidOutput(
            String::from_utf8_lossy(&output.stdout).to_string(),
        ))?;

    if parts.len() != 4 {
        return Err(Error::InvalidOutput(
            String::from_utf8_lossy(&output.stdout).to_string(),
        ));
    }

    Ok(Region {
        x: parts[0].into(),
        y: parts[1].into(),
        width: parts[2].into(),
        height: parts[3].into(),
    })
}

fn parse_u16_from_ascii(bytes: &[u8]) -> Option<u16> {
    let mut result = 0u16;
    for &byte in bytes {
        match byte {
            b'0'..=b'9' => result = result * 10 + u16::from(byte - b'0'),
            _ => return None,
        }
    }
    Some(result)
}
