use serde_json::Value;

/// Your json MUST BE valid and generated by `serde_json::to_vec` before being
/// sent in this function. This function is DUMB and FAST but makes a lot of
/// asumption about the way `serde_json` will generate its input.
///
/// Will return `true` if the JSON contains an object, an array of array
/// or an array containing an object. Returns `false` for everything else.
pub fn should_flatten_from_unchecked_slice(json: &[u8]) -> bool {
    if json.is_empty() {
        return false;
    }

    // since the json we receive has been generated by serde_json we know
    // it doesn't contains any whitespace at the beginning thus we can check
    // directly if we're looking at an object.
    if json[0] == b'{' {
        return true;
    } else if json[0] != b'[' {
        // if the json isn't an object or an array it means it's a simple value.
        return false;
    }

    // The array case is a little bit more complex. We are looking for a second
    // `[` but we need to ensure that it doesn't appear inside of a string. Thus
    // we need to keep track of if we're in a string or not.

    // will be used when we met a `\` to skip the next character.
    let mut skip_next = false;
    let mut in_string = false;

    for byte in json.iter().skip(1) {
        match byte {
            // handle the backlash.
            _ if skip_next => skip_next = false,
            b'\\' => skip_next = true,

            // handle the strings.
            byte if in_string => {
                if *byte == b'"' {
                    in_string = false;
                }
            }
            b'"' => in_string = true,

            // handle the arrays.
            b'[' => return true,
            // since we know the json is valid we don't need to ensure the
            // array is correctly closed

            // handle the objects.
            b'{' => return true,

            // ignore everything else
            _ => (),
        }
    }

    false
}

/// Consider using [`should_flatten_from_unchecked_slice`] when you can.
/// Will returns `true` if the json contains an object, an array of array
/// or an array containing an object.
/// Returns `false` for everything else.
/// This function has been written to test the [`should_flatten_from_unchecked_slice`].
pub fn should_flatten_from_value(json: &Value) -> bool {
    match json {
        Value::Object(..) => true,
        Value::Array(array) => array.iter().any(|value| value.is_array() || value.is_object()),
        _ => false,
    }
}

#[cfg(test)]
mod tests {
    use serde_json::*;

    use super::*;

    #[test]
    fn test_shouldnt_flatten() {
        let shouldnt_flatten = vec![
            json!(null),
            json!(true),
            json!(false),
            json!("a superb string"),
            json!("a string escaping other \"string\""),
            json!([null, true, false]),
            json!(["hello", "world", "!"]),
            json!(["a \"string\" escaping 'an other'", "\"[\"", "\"{\""]),
        ];
        for value in shouldnt_flatten {
            assert!(!should_flatten_from_value(&value));
            let value = serde_json::to_vec(&value).unwrap();
            assert!(!should_flatten_from_unchecked_slice(&value));
        }
    }

    #[test]
    fn test_should_flatten() {
        let should_flatten = vec![
            json!({}),
            json!({ "hello": "world" }),
            json!(["hello", ["world"]]),
            json!([true, true, true, true, true, true, true, true, true, {}]),
        ];
        for value in should_flatten {
            assert!(should_flatten_from_value(&value));
            let value = serde_json::to_vec(&value).unwrap();
            assert!(should_flatten_from_unchecked_slice(&value));
        }
    }
}
