// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (words) bogusfile emptyfile abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu

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

static INPUT: &str = "lorem_ipsum.txt";

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

#[test]
fn test_stdin_default() {
    new_ucmd!()
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_default.expected");
}

#[test]
fn test_stdin_1_line_obsolete() {
    new_ucmd!()
        .args(&["-1"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_1_line.expected");
}

#[test]
fn test_stdin_1_line() {
    new_ucmd!()
        .args(&["-n", "1"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_1_line.expected");
}

#[test]
fn test_stdin_negative_23_line() {
    new_ucmd!()
        .args(&["-n", "-23"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_1_line.expected");
}

#[test]
fn test_stdin_5_chars() {
    new_ucmd!()
        .args(&["-c", "5"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_5_chars.expected");
}

#[test]
fn test_single_default() {
    new_ucmd!()
        .arg(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_default.expected");
}

#[test]
fn test_single_1_line_obsolete() {
    new_ucmd!()
        .args(&["-1", INPUT])
        .run()
        .stdout_is_fixture("lorem_ipsum_1_line.expected");
}

#[test]
fn test_single_1_line() {
    new_ucmd!()
        .args(&["-n", "1", INPUT])
        .run()
        .stdout_is_fixture("lorem_ipsum_1_line.expected");
}

#[test]
fn test_single_5_chars() {
    new_ucmd!()
        .args(&["-c", "5", INPUT])
        .run()
        .stdout_is_fixture("lorem_ipsum_5_chars.expected");
}

#[test]
fn test_verbose() {
    new_ucmd!()
        .args(&["-v", INPUT])
        .run()
        .stdout_is_fixture("lorem_ipsum_verbose.expected");
}

#[test]
#[ignore]
fn test_spams_newline() {
    //this test is does not mirror what GNU does
    new_ucmd!().pipe_in("a").succeeds().stdout_is("a\n");
}

#[test]
fn test_byte_syntax() {
    new_ucmd!()
        .args(&["-1c"])
        .pipe_in("abc")
        .run()
        .stdout_is("a");
}

#[test]
fn test_line_syntax() {
    new_ucmd!()
        .args(&["-n", "2048m"])
        .pipe_in("a\n")
        .run()
        .stdout_is("a\n");
}

#[test]
fn test_zero_terminated_syntax() {
    new_ucmd!()
        .args(&["-z", "-n", "1"])
        .pipe_in("x\0y")
        .run()
        .stdout_is("x\0");
}

#[test]
fn test_zero_terminated_syntax_2() {
    new_ucmd!()
        .args(&["-z", "-n", "2"])
        .pipe_in("x\0y")
        .run()
        .stdout_is("x\0y");
}

#[test]
fn test_zero_terminated_negative_lines() {
    new_ucmd!()
        .args(&["-z", "-n", "-1"])
        .pipe_in("x\0y\0z\0")
        .run()
        .stdout_is("x\0y\0");
}

#[test]
fn test_negative_byte_syntax() {
    new_ucmd!()
        .args(&["--bytes=-2"])
        .pipe_in("a\n")
        .run()
        .stdout_is("");
}

#[test]
fn test_negative_zero_lines() {
    new_ucmd!()
        .arg("--lines=-0")
        .pipe_in("a\nb\n")
        .succeeds()
        .stdout_is("a\nb\n");
    new_ucmd!()
        .arg("--lines=-0")
        .pipe_in("a\nb")
        .succeeds()
        .stdout_is("a\nb");
}

#[test]
fn test_negative_zero_bytes() {
    new_ucmd!()
        .args(&["--bytes=-0"])
        .pipe_in("qwerty")
        .succeeds()
        .stdout_is("qwerty");
}
#[test]
fn test_no_such_file_or_directory() {
    new_ucmd!()
        .arg("no_such_file.toml")
        .fails()
        .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
}

#[test]
fn test_lines_leading_zeros() {
    new_ucmd!()
        .arg("--lines=010")
        .pipe_in("\n\n\n\n\n\n\n\n\n\n\n\n")
        .succeeds()
        .stdout_is("\n\n\n\n\n\n\n\n\n\n");
}

/// Test that each non-existent files gets its own error message printed.
#[test]
fn test_multiple_nonexistent_files() {
    new_ucmd!()
        .args(&["bogusfile1", "bogusfile2"])
        .fails()
        .stdout_does_not_contain("==> bogusfile1 <==")
        .stderr_contains("cannot open 'bogusfile1' for reading: No such file or directory")
        .stdout_does_not_contain("==> bogusfile2 <==")
        .stderr_contains("cannot open 'bogusfile2' for reading: No such file or directory");
}

// there was a bug not caught by previous tests
// where for negative n > 3, the total amount of lines
// was correct, but it would eat from the second line
#[test]
fn test_sequence_fixture() {
    new_ucmd!()
        .args(&["-n", "-10", "sequence"])
        .run()
        .stdout_is_fixture("sequence.expected");
}
#[test]
fn test_file_backwards() {
    new_ucmd!()
        .args(&["-c", "-10", "lorem_ipsum.txt"])
        .run()
        .stdout_is_fixture("lorem_ipsum_backwards_file.expected");
}

#[test]
fn test_zero_terminated() {
    new_ucmd!()
        .args(&["-z", "zero_terminated.txt"])
        .run()
        .stdout_is_fixture("zero_terminated.expected");
}

#[test]
fn test_obsolete_extras() {
    new_ucmd!()
        .args(&["-5zv"])
        .pipe_in("1\x002\x003\x004\x005\x006")
        .succeeds()
        .stdout_is("==> standard input <==\n1\x002\x003\x004\x005\0");
}

#[test]
fn test_multiple_files() {
    new_ucmd!()
        .args(&["emptyfile.txt", "emptyfile.txt"])
        .succeeds()
        .stdout_is("==> emptyfile.txt <==\n\n==> emptyfile.txt <==\n");
}

#[test]
fn test_multiple_files_with_stdin() {
    new_ucmd!()
        .args(&["emptyfile.txt", "-", "emptyfile.txt"])
        .pipe_in("hello\n")
        .succeeds()
        .stdout_is(
            "==> emptyfile.txt <==

==> standard input <==
hello

==> emptyfile.txt <==
",
        );
}

#[test]
fn test_bad_utf8() {
    let bytes: &[u8] = b"\xfc\x80\x80\x80\x80\xaf";
    new_ucmd!()
        .args(&["-c", "6"])
        .pipe_in(bytes)
        .succeeds()
        .stdout_is_bytes(bytes);
}

#[test]
fn test_bad_utf8_lines() {
    let input: &[u8] =
        b"\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf";
    let output = b"\xfc\x80\x80\x80\x80\xaf\nb\xfc\x80\x80\x80\x80\xaf\n";
    new_ucmd!()
        .args(&["-n", "2"])
        .pipe_in(input)
        .succeeds()
        .stdout_is_bytes(output);
}

#[test]
fn test_head_invalid_num() {
    new_ucmd!()
        .args(&["-c", "1024R", "emptyfile.txt"])
        .fails()
        .stderr_is(
            "head: invalid number of bytes: '1024R': Value too large for defined data type\n",
        );
    new_ucmd!()
        .args(&["-n", "1024R", "emptyfile.txt"])
        .fails()
        .stderr_is(
            "head: invalid number of lines: '1024R': Value too large for defined data type\n",
        );
    #[cfg(not(target_pointer_width = "128"))]
    new_ucmd!()
        .args(&["-c", "1Y", "emptyfile.txt"])
        .fails()
        .stderr_is("head: invalid number of bytes: '1Y': Value too large for defined data type\n");
    #[cfg(not(target_pointer_width = "128"))]
    new_ucmd!()
        .args(&["-n", "1Y", "emptyfile.txt"])
        .fails()
        .stderr_is("head: invalid number of lines: '1Y': Value too large for defined data type\n");
    #[cfg(target_pointer_width = "32")]
    {
        let sizes = ["1000G", "10T"];
        for size in &sizes {
            new_ucmd!().args(&["-c", size]).succeeds();
        }
    }
    #[cfg(target_pointer_width = "32")]
    {
        let sizes = ["-1000G", "-10T"];
        for size in &sizes {
            new_ucmd!()
                .args(&["-c", size])
                .fails()
                .stderr_is("head: out of range integral type conversion attempted: number of -bytes or -lines is too large\n");
        }
    }
    new_ucmd!()
        .args(&["-c", "-³"])
        .fails()
        .stderr_is("head: invalid number of bytes: '³'\n");
}

#[test]
fn test_head_num_with_undocumented_sign_bytes() {
    // tail: '-' is not documented (8.32 man pages)
    // head: '+' is not documented (8.32 man pages)
    const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
    new_ucmd!()
        .args(&["-c", "5"])
        .pipe_in(ALPHABET)
        .succeeds()
        .stdout_is("abcde");
    new_ucmd!()
        .args(&["-c", "-5"])
        .pipe_in(ALPHABET)
        .succeeds()
        .stdout_is("abcdefghijklmnopqrstu");
    new_ucmd!()
        .args(&["-c", "+5"])
        .pipe_in(ALPHABET)
        .succeeds()
        .stdout_is("abcde");
}

#[test]
fn test_presume_input_pipe_default() {
    new_ucmd!()
        .args(&["---presume-input-pipe"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_default.expected");
}

#[test]
fn test_presume_input_pipe_5_chars() {
    new_ucmd!()
        .args(&["-c", "5", "---presume-input-pipe"])
        .pipe_in_fixture(INPUT)
        .run()
        .stdout_is_fixture("lorem_ipsum_5_chars.expected");
}

#[cfg(all(
    not(target_os = "windows"),
    not(target_os = "macos"),
    not(target_os = "android"),
    not(target_os = "freebsd")
))]
#[test]
fn test_read_backwards_bytes_proc_fs_version() {
    let ts = TestScenario::new(util_name!());

    let args = ["-c", "-1", "/proc/version"];
    let result = ts.ucmd().args(&args).succeeds();
    assert!(!result.stdout().is_empty());
}

#[cfg(all(
    not(target_os = "windows"),
    not(target_os = "macos"),
    not(target_os = "android"),
    not(target_os = "freebsd")
))]
#[test]
fn test_read_backwards_bytes_proc_fs_modules() {
    let ts = TestScenario::new(util_name!());

    let args = ["-c", "-1", "/proc/modules"];
    let result = ts.ucmd().args(&args).succeeds();
    assert!(!result.stdout().is_empty());
}

#[cfg(all(
    not(target_os = "windows"),
    not(target_os = "macos"),
    not(target_os = "android"),
    not(target_os = "freebsd")
))]
#[test]
fn test_read_backwards_lines_proc_fs_modules() {
    let ts = TestScenario::new(util_name!());

    let args = ["--lines", "-1", "/proc/modules"];
    let result = ts.ucmd().args(&args).succeeds();
    assert!(!result.stdout().is_empty());
}

#[cfg(all(
    not(target_os = "windows"),
    not(target_os = "macos"),
    not(target_os = "android"),
    not(target_os = "freebsd")
))]
#[test]
fn test_read_backwards_bytes_sys_kernel_profiling() {
    let ts = TestScenario::new(util_name!());

    let args = ["-c", "-1", "/sys/kernel/profiling"];
    let result = ts.ucmd().args(&args).succeeds();
    let stdout_str = result.stdout_str();
    assert_eq!(stdout_str.len(), 1);
    assert!(stdout_str == "0" || stdout_str == "1");
}
