macro_rules! help_msg {
    () => {
        concat!(
            version_msg!(),
            "\n",
            indoc::indoc!(r#"
            Usage:
              aki-json-pick [options]

            The json pick out command.

            Options:
                  --color <when>        json colored output.
              -s, --select <selector>   pick out json value by <selector>.
              -p, --pretty              pretty output.
              -r, --raw-output          raw string output without JSON double-quote.

              -H, --help        display this help and exit
              -V, --version     display version information and exit
              -X <x-options>    x options. try -X help

            Option Parameters:
              <when>        'always', 'never', or 'auto'
              <selector>    json selector

            Examples:
              pick out some.property value:
                echo -e '{ "some": { "property": "yay!" } }' | aki-json-pick -s '"some"."property"'
            "#),
            "\n",
        )
    };
}

macro_rules! try_help_msg {
    () => {
        "Try --help for help.\n"
    };
}

macro_rules! program_name {
    () => {
        "aki-json-pick"
    };
}

macro_rules! version_msg {
    () => {
        concat!(program_name!(), " ", env!("CARGO_PKG_VERSION"), "\n")
    };
}

/*
macro_rules! fixture_text10k {
    () => {
        "fixtures/text10k.txt"
    };
}
*/

macro_rules! do_execute {
    ($args:expr) => {
        do_execute!($args, "")
    };
    ($args:expr, $sin:expr) => {{
        let sioe = RunnelIoe::new(
            Box::new(StringIn::with_str($sin)),
            Box::new(StringOut::default()),
            Box::new(StringErr::default()),
        );
        let program = env!("CARGO_PKG_NAME");
        let r = execute(&sioe, &program, $args);
        match r {
            Ok(_) => {}
            Err(ref err) => {
                let _ = sioe
                    .perr()
                    .lock()
                    .write_fmt(format_args!("{}: {}\n", program, err));
            }
        };
        (r, sioe)
    }};
}

macro_rules! buff {
    ($sioe:expr, serr) => {
        $sioe.perr().lock().buffer_str()
    };
    ($sioe:expr, sout) => {
        $sioe.pout().lock().buffer_str()
    };
}

mod test_s0 {
    use libaki_json_pick::*;
    use runnel::medium::stringio::{StringErr, StringIn, StringOut};
    use runnel::RunnelIoe;
    use std::io::Write;
    //
    #[test]
    fn test_help() {
        let (r, sioe) = do_execute!(&["-H"]);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), help_msg!());
        assert_eq!(r.is_ok(), true);
    }
    #[test]
    fn test_help_long() {
        let (r, sioe) = do_execute!(&["--help"]);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), help_msg!());
        assert_eq!(r.is_ok(), true);
    }
    #[test]
    fn test_version() {
        let (r, sioe) = do_execute!(&["-V"]);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), version_msg!());
        assert_eq!(r.is_ok(), true);
    }
    #[test]
    fn test_version_long() {
        let (r, sioe) = do_execute!(&["--version"]);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), version_msg!());
        assert_eq!(r.is_ok(), true);
    }
    #[test]
    fn test_non_option() {
        let (r, sioe) = do_execute!(&[""]);
        #[rustfmt::skip]
        assert_eq!(
            buff!(sioe, serr),
            concat!(
                program_name!(), ": ",
                "Missing option: s\n",
                "Unexpected argument: \n",
                try_help_msg!()
            )
        );
        assert_eq!(buff!(sioe, sout), "");
        assert_eq!(r.is_ok(), false);
    }
}

const IN_DAT_01: &str = include_str!("../fixtures/01.json");
const IN_DAT_02: &str = include_str!("../fixtures/02.json");
const IN_DAT_03: &str = include_str!("../fixtures/03.json");
const IN_DAT_04: &str = include_str!("../fixtures/04.json");
const IN_DAT_05: &str = include_str!("../fixtures/05.json");
const IN_DAT_06: &str = include_str!("../fixtures/06.json");
const IN_DAT_07: &str = include_str!("../fixtures/07.json");
const IN_DAT_08: &str = include_str!("../fixtures/08.json");
const IN_DAT_09: &str = include_str!("../fixtures/09.json");
const IN_DAT_10: &str = include_str!("../fixtures/10.json");
const IN_DAT_11: &str = include_str!("../fixtures/11.json");

mod test_s1 {
    use libaki_json_pick::*;
    use runnel::medium::stringio::{StringErr, StringIn, StringOut};
    use runnel::RunnelIoe;
    use std::io::Write;
    //
    #[test]
    fn test_t01() {
        // Root selection
        let (r, sioe) = do_execute!(&["-s", "."], super::IN_DAT_01);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "\"This is a valid JSON text with one value\"\n",
        );
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t02() {
        // Child selection
        let (r, sioe) = do_execute!(&["-s", "\"some\".\"property\""], super::IN_DAT_02);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "\"yay!\"\n",);
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t03() {
        // Index selection
        let (r, sioe) = do_execute!(&["-s", "\"primes\".[0]"], super::IN_DAT_03);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "7\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"primes\"[0]"], super::IN_DAT_03);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "7\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"primes\"[2,0]"], super::IN_DAT_03);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[13,7]\n",);
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t04() {
        // Range selection
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[1:2]"], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[{\"second\":\"Kitkat\"},{\"third\":\"Misty\"}]\n",
        );
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[2:1]"], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[{\"third\":\"Misty\"},{\"second\":\"Kitkat\"}]\n",
        );
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[2:1].[1:0]"], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[{\"second\":\"Kitkat\"},{\"third\":\"Misty\"}]\n",
        );
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[2:1].[0].\"third\""], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "\"Misty\"\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[1:]"], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[{\"second\":\"Kitkat\"},{\"third\":\"Misty\"}]\n",
        );
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"cats\".[:1]"], super::IN_DAT_04);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[{\"first\":\"Pixie\"},{\"second\":\"Kitkat\"}]\n",
        );
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t05() {
        // Array selection
        let (r, sioe) = do_execute!(&["-s", "\"primes\".[]"], super::IN_DAT_05);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[7,11,13]\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"primes\".[0:2]"], super::IN_DAT_05);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[7,11,13]\n",);
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t06() {
        // Property selection
        let (r, sioe) = do_execute!(&["-s", "\"object\".{\"a\",\"c\"}"], super::IN_DAT_06);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "{\"a\":1,\"c\":3}\n",);
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t07() {
        // Multi-selection
        let (r, sioe) = do_execute!(&["-s", "\"one\".[2:0],\"two\",\"three\""], super::IN_DAT_07);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[[3,2,1],2,3]\n",);
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t08() {
        // Filter
        let (r, sioe) = do_execute!(&["-s", "\"laptops\"|\"laptop\""], super::IN_DAT_08);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[{\"brand\":\"Apple\",\"options\":[\"a\",\"b\",\"c\"]},{\"brand\":\"Asus\",\"options\":[\"d\",\"e\",\"f\"]}]\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(
            &["-s", "\"laptops\"|\"laptop\".\"brand\""],
            super::IN_DAT_08
        );
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "[\"Apple\",\"Asus\"]\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(
            &[
                "-s",
                "\"laptops\".[1:0]|\"laptop\".\"brand\",\"laptops\"|\"laptop\".\"brand\""
            ],
            super::IN_DAT_08
        );
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[[\"Asus\",\"Apple\"],[\"Apple\",\"Asus\"]]\n",
        );
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(
            &[
                "-s",
                "\"laptops\".[1:0]|\"laptop\"|\"brand\",\"laptops\"|\"laptop\"|\"brand\""
            ],
            super::IN_DAT_08
        );
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[[\"Asus\",\"Apple\"],[\"Apple\",\"Asus\"]]\n",
        );
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t09() {
        // Flatten arrays
        let (r, sioe) = do_execute!(&["-s", "..\"dna\""], super::IN_DAT_09);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "[\"c\",\"a\",\"c\",\"g\",\"t\",\"a\",\"t\"]\n",
        );
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t10() {
        // Truncate
        let (r, sioe) = do_execute!(&["-s", ".!"], super::IN_DAT_10);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "{\"foo\":{}}\n",);
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"foo\"!"], super::IN_DAT_10);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(
            buff!(sioe, sout),
            "{\"a\":null,\"b\":\"bar\",\"c\":1337,\"d\":{}}\n",
        );
        assert_eq!(r.is_ok(), true);
    }
    //
    #[test]
    fn test_t11() {
        // Special characters
        let (r, sioe) = do_execute!(&["-s", "\".valid\""], super::IN_DAT_11);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "1337\n");
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"\""], super::IN_DAT_11);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "\"yeah!\"\n");
        assert_eq!(r.is_ok(), true);
        //
        let (r, sioe) = do_execute!(&["-s", "\"\\\"\""], super::IN_DAT_11);
        assert_eq!(buff!(sioe, serr), "");
        assert_eq!(buff!(sioe, sout), "\"yup, valid too!\"\n");
        assert_eq!(r.is_ok(), true);
    }
}
