use std::ffi::OsStr;
use std::process::{ExitStatus, Stdio};

#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;

use crate::common::util::TestScenario;

#[cfg(unix)]
fn check_termination(result: ExitStatus) {
    assert_eq!(result.signal(), Some(libc::SIGPIPE));
}

#[cfg(not(unix))]
fn check_termination(result: ExitStatus) {
    assert!(result.success(), "yes did not exit successfully");
}

const NO_ARGS: &[&str] = &[];

/// Run `yes`, capture some of the output, close the pipe, and verify it.
fn run(args: &[impl AsRef<OsStr>], expected: &[u8]) {
    let mut cmd = new_ucmd!();
    let mut child = cmd.args(args).set_stdout(Stdio::piped()).run_no_wait();
    let buf = child.stdout_exact_bytes(expected.len());
    child.close_stdout();

    #[allow(deprecated)]
    check_termination(child.wait_with_output().unwrap().status);
    assert_eq!(buf.as_slice(), expected);
}

#[test]
fn test_invalid_arg() {
    new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}

#[test]
fn test_simple() {
    run(NO_ARGS, b"y\ny\ny\ny\n");
}

#[test]
fn test_args() {
    run(&["a", "bar", "c"], b"a bar c\na bar c\na ba");
}

#[test]
fn test_long_output() {
    run(NO_ARGS, "y\n".repeat(512 * 1024).as_bytes());
}

/// Test with an output that seems likely to get mangled in case of incomplete writes.
#[test]
fn test_long_odd_output() {
    run(&["abcdef"], "abcdef\n".repeat(1024 * 1024).as_bytes());
}

/// Test with an input that doesn't fit in the standard buffer.
#[test]
fn test_long_input() {
    #[cfg(not(windows))]
    const TIMES: usize = 14000;
    // On Windows the command line is limited to 8191 bytes.
    // This is not actually enough to fill the buffer, but it's still nice to
    // try something long.
    #[cfg(windows)]
    const TIMES: usize = 500;
    let arg = "abcdef".repeat(TIMES) + "\n";
    let expected_out = arg.repeat(30);
    run(&[&arg[..arg.len() - 1]], expected_out.as_bytes());
}

#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
fn test_piped_to_dev_full() {
    use std::fs::OpenOptions;

    for append in [true, false] {
        {
            let dev_full = OpenOptions::new()
                .write(true)
                .append(append)
                .open("/dev/full")
                .unwrap();

            new_ucmd!()
                .set_stdout(dev_full)
                .fails()
                .stderr_contains("No space left on device");
        }
    }
}

#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn test_non_utf8() {
    #[cfg(unix)]
    use std::os::unix::ffi::OsStrExt;
    #[cfg(target_os = "wasi")]
    use std::os::wasi::ffi::OsStrExt;

    run(
        &[
            OsStr::from_bytes(b"\xbf\xff\xee"),
            OsStr::from_bytes(b"bar"),
        ],
        &b"\xbf\xff\xee bar\n".repeat(5000),
    );
}
