// License: see LICENSE file at root directory of `master` branch

#![cfg(feature="std")]

use {
    core::convert::TryFrom,
    std::io::Write,
    sj::{IoResult, Value}
};

#[derive(Debug)]
enum JsonFile {
    Array,
    UglyArray,
    NiceArray,

    Object,
    UglyObject,
    NiceObject,

    Ugly,
    Nice,
}

impl JsonFile {

    /// # Loads
    fn load<'a>(&self) -> &'a str {
        let (result, should_trim) = match self {
            JsonFile::Array => (include_str!("array.json"), true),
            JsonFile::UglyArray => (include_str!("ugly-array.json"), false),
            JsonFile::NiceArray => (include_str!("nice-array.json"), true),

            JsonFile::Object => (include_str!("object.json"), true),
            JsonFile::UglyObject => (include_str!("ugly-object.json"), false),
            JsonFile::NiceObject => (include_str!("nice-object.json"), true),

            JsonFile::Ugly => (include_str!("ugly.json"), false),
            JsonFile::Nice => (include_str!("nice.json"), true),
        };
        // Ignore license header
        let result = &result[result.find('\n').unwrap()..];
        match should_trim {
            true => result.trim(),
            false => result,
        }
    }

}

#[test]
fn arrays() -> IoResult<()> {
    match sj::parse_bytes("[]")? {
        Value::Array(array) => assert!(array.is_empty()),
        other => panic!("Expected array, got: {:?}", other),
    };

    let value = sj::parse_bytes(JsonFile::UglyArray.load())?;
    match value {
        Value::Array(ref array) => match (array.first(), array.last(), array.len()) {
            (Some(Value::String(s)), Some(Value::Array(sub_array)), 2) if s == "some" => match (
                sub_array.first(), sub_array.last(), sub_array.len(),
            ) {
                (Some(Value::Array(last_array)), Some(Value::Object(object)), 2) if last_array.is_empty() && object.is_empty()
                => for (json, expected) in &[
                    (value.format()?, JsonFile::Array.load()),
                    (value.format_nicely(Some(2))?, JsonFile::NiceArray.load()),
                ] {
                    assert_eq!(json, expected);
                },
                other => panic!("Expected last array to be empty, got: {:?}", other),
            },
            other => panic!("Expected array with one string and one sub array, got: {:?}", other),
        },
        other => panic!("Expected array, got: {:?}", other),
    };

    Ok(())
}

#[test]
fn objects() -> IoResult<()> {
    match sj::parse_bytes("{}")? {
        Value::Object(object) => assert!(object.is_empty()),
        other => panic!("Expected object, got: {:?}", other),
    };

    let value = sj::parse_bytes(JsonFile::UglyObject.load())?;
    match value {
        Value::Object(ref object) => match (object.get("some"), object.get("u8"), object.len()) {
            (Some(Value::Array(array)), Some(Value::Number(n)), 2) => match (u8::try_from(n), array.first(), array.len()) {
                (Ok(99), Some(Value::String(s)), 1) if s == "thing" => for (json, expected) in &[
                    (value.format()?, JsonFile::Object.load()),
                    (value.format_nicely(None)?, JsonFile::NiceObject.load()),
                ] {
                    assert_eq!(json, expected);
                },
                _ => panic!("Expected array with one string, got: {:?}", array),
            },
            _ => panic!("Expected object with two pairs, got: {:?}", object),
        },
        other => panic!("Expected object, got: {:?}", other),
    };

    Ok(())
}

#[test]
fn numbers() -> IoResult<()> {
    let value = sj::parse_bytes("[10, -0, -1.0, 10.5E+5, -99e-6, -6e7, null, true, false]")?;
    match value {
        Value::Array(ref a) if a.len() == 9 => match (
            a.get(0), a.get(1), a.get(2), a.get(3), a.get(4), a.get(5), a.get(6), a.get(7), a.get(8),
        ) {
            (
                Some(Value::Number(n0)), Some(Value::Number(n1)), Some(Value::Number(n2)), Some(Value::Number(n3)), Some(Value::Number(n4)),
                Some(Value::Number(n5)), Some(Value::Null), Some(Value::Boolean(true)), Some(Value::Boolean(false)),
            ) => match (u8::try_from(n0), i8::try_from(n1), f64::try_from(n2), f64::try_from(n3), f64::try_from(n4), f64::try_from(n5)) {
                (Ok(10), Ok(0), Ok(n2), Ok(n3), Ok(n4), Ok(n5)) if n2 == -1.0 && n3 == 10.5e5 && n4 == -99e-6 && n5 == -6e7 => {
                    std::println!("{}", value.format_nicely(None)?);
                },
                other => panic!("Expected 5 primitive numbers, 1 error, 1 null and 2 booleans, got: {:?}", other),
            },
            _ => panic!("Expected 9 items, got: {:?}", a),
        },
        _ => panic!("Expected array of 9 items, got: {:?}", value),
    };

    Ok(())
}

#[test]
fn formats() -> IoResult<()> {
    let value = sj::parse_bytes(JsonFile::Ugly.load())?;

    let formatted = value.format_nicely_as_bytes(None)?;
    let expected = JsonFile::Nice.load().as_bytes();

    assert_eq!(formatted, expected);

    let mut buf = Vec::with_capacity(formatted.len());
    value.write_nicely(None, &mut buf)?;
    buf.flush()?;
    assert_eq!(buf, expected);

    Ok(())
}
