use std::borrow::ToOwned;
use std::process;

use crate::workdir::Workdir;

macro_rules! slice_tests {
    ($name:ident, $start:expr, $end:expr, $expected:expr) => {
        mod $name {
            use super::test_slice;

            #[test]
            fn headers() {
                let name = concat!(stringify!($name), "headers");
                test_slice(name, $start, $end, $expected, true, false);
            }

            #[test]
            fn no_headers() {
                let name = concat!(stringify!($name), "no_headers");
                test_slice(name, $start, $end, $expected, false, false);
            }

            #[test]
            fn headers_len() {
                let name = concat!(stringify!($name), "headers_len");
                test_slice(name, $start, $end, $expected, true, true);
            }

            #[test]
            fn no_headers_len() {
                let name = concat!(stringify!($name), "no_headers_len");
                test_slice(name, $start, $end, $expected, false, true);
            }
        }
    };
}

fn setup(name: &str, headers: bool) -> (Workdir, process::Command) {
    let wrk = Workdir::new(name);
    let mut data = vec![svec!["a"], svec!["b"], svec!["c"], svec!["d"], svec!["e"]];
    if headers {
        data.insert(0, svec!["header"]);
    }

    wrk.create("in.csv", data);

    let mut cmd = wrk.command("slice");
    cmd.arg("in.csv");

    (wrk, cmd)
}

fn test_slice(
    name: &str,
    start: Option<usize>,
    end: Option<usize>,
    expected: &[&str],
    headers: bool,
    as_len: bool,
) {
    let (wrk, mut cmd) = setup(name, headers);
    if let Some(start) = start {
        cmd.arg("--start").arg(&start.to_string());
    }
    if let Some(end) = end {
        if as_len {
            let start = start.unwrap_or(0);
            cmd.arg("--len").arg(&(end - start).to_string());
        } else {
            cmd.arg("--end").arg(&end.to_string());
        }
    }
    if !headers {
        cmd.arg("--no-headers");
    }

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let mut expected = expected
        .iter()
        .map(|&s| vec![s.to_owned()])
        .collect::<Vec<Vec<String>>>();
    if headers {
        expected.insert(0, svec!["header"]);
    }
    assert_eq!(got, expected);
}

fn test_index(name: &str, idx: usize, expected: &str, headers: bool) {
    let (wrk, mut cmd) = setup(name, headers);
    cmd.arg("--index").arg(&idx.to_string());
    if !headers {
        cmd.arg("--no-headers");
    }

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let mut expected = vec![vec![expected.to_owned()]];
    if headers {
        expected.insert(0, svec!["header"]);
    }
    assert_eq!(got, expected);
}

slice_tests!(slice_simple, Some(0), Some(1), &["a"]);
slice_tests!(slice_simple_2, Some(1), Some(3), &["b", "c"]);
slice_tests!(slice_no_start, None, Some(1), &["a"]);
slice_tests!(slice_no_end, Some(3), None, &["d", "e"]);
slice_tests!(slice_all, None, None, &["a", "b", "c", "d", "e"]);

#[test]
fn slice_index() {
    test_index("slice_index", 1, "b", true);
}
#[test]
fn slice_index_no_headers() {
    test_index("slice_index_no_headers", 1, "b", false);
}

#[test]
fn slice_indices() {
    let wrk = Workdir::new("slice_indices");
    wrk.create(
        "data.csv",
        vec![
            svec!["n"],
            svec!["zero"],
            svec!["one"],
            svec!["two"],
            svec!["three"],
            svec!["four"],
            svec!["five"],
        ],
    );
    let mut cmd = wrk.command("slice");
    cmd.args(["-I", "1,5,4"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["one"], svec!["four"], svec!["five"]];
    assert_eq!(got, expected);
}

#[test]
fn slice_byte_offset() {
    let wrk = Workdir::new("slice_byte_offset");
    wrk.create(
        "data.csv",
        vec![
            svec!["n"],
            svec!["zero"],
            svec!["one"],
            svec!["two"],
            svec!["three"],
            svec!["four"],
            svec!["five"],
        ],
    );

    let mut cmd = wrk.command("slice");
    cmd.args(["-B", "10"]).args(["-l", "1"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["two"]];
    assert_eq!(got, expected);

    let mut cmd = wrk.command("slice");
    cmd.args(["-B", "10"])
        .args(["--end-byte", "18"])
        .arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["two"], svec!["thr"]];
    assert_eq!(got, expected);
}

#[test]
fn slice_conditions() {
    let wrk = Workdir::new("slice_conditions");
    wrk.create(
        "data.csv",
        vec![
            svec!["n"],
            svec!["0"],
            svec!["1"],
            svec!["2"],
            svec!["3"],
            svec!["4"],
            svec!["5"],
        ],
    );

    let mut cmd = wrk.command("slice");
    cmd.args(["-S", "n > 3"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["4"], svec!["5"]];
    assert_eq!(got, expected);

    let mut cmd = wrk.command("slice");
    cmd.args(["-S", "n > 3"]).args(["-l", "1"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["4"]];
    assert_eq!(got, expected);

    let mut cmd = wrk.command("slice");
    cmd.args(["-E", "n >= 2"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["0"], svec!["1"]];
    assert_eq!(got, expected);

    let mut cmd = wrk.command("slice");
    cmd.args(["-S", "n > 1"])
        .args(["-E", "n >= 3"])
        .arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["2"]];
    assert_eq!(got, expected);
}

#[test]
fn slice_last() {
    let wrk = Workdir::new("slice_last");
    wrk.create(
        "data.csv",
        vec![
            svec!["n"],
            svec!["zero"],
            svec!["one"],
            svec!["two"],
            svec!["three"],
            svec!["four"],
            svec!["five"],
        ],
    );
    let mut cmd = wrk.command("slice");
    cmd.args(["-L", "3"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["n"], svec!["three"], svec!["four"], svec!["five"]];
    assert_eq!(got, expected);

    let mut cmd = wrk.command("slice");
    cmd.args(["-L", "300"]).arg("data.csv");

    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![
        svec!["n"],
        svec!["zero"],
        svec!["one"],
        svec!["two"],
        svec!["three"],
        svec!["four"],
        svec!["five"],
    ];
    assert_eq!(got, expected);
}