use js_sys::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

#[wasm_bindgen_test]
fn parse_array() {
    let js_array = JSON::parse("[1, 2, 3]").unwrap();;
    assert!(Array::is_array(&js_array));

    let array = Array::from(&js_array);
    assert_eq!(array.length(), 3);
    assert_eq!(array.pop(), 3);
    assert_eq!(array.pop(), 2);
    assert_eq!(array.pop(), 1);
}

#[wasm_bindgen_test]
fn parse_object() {
    let js_object = JSON::parse("{\"x\": 5, \"y\": true, \"z\": [\"foo\", \"bar\"]}").unwrap();
    assert!(js_object.is_object());

    let obj = Object::from(js_object);
    let keys = Object::keys(&obj);
    assert_eq!(keys.length(), 3);
    assert_eq!(keys.pop().as_string().unwrap(), "z");
    assert_eq!(keys.pop().as_string().unwrap(), "y");
    assert_eq!(keys.pop().as_string().unwrap(), "x");

    let values = Object::values(&obj);
    assert_eq!(values.length(), 3);

    let z = values.pop();
    assert!(Array::is_array(&z));
    let z_array = Array::from(&z);
    assert_eq!(z_array.length(), 2);

    let y = values.pop();
    assert_eq!(y.as_bool(), Some(true));

    let x = values.pop();
    assert!(Number::is_integer(&x));
    let x_num = Number::new(&x);
    assert_eq!(x_num.value_of(), 5.0);
}

#[wasm_bindgen_test]
fn parse_error() {
    let js_object = JSON::parse("invalid json");
    assert!(js_object.is_err());
    let err = js_object.unwrap_err();
    assert!(err.is_instance_of::<Error>());
}

#[wasm_bindgen_test]
fn stringify() {
    let arr = Array::new();
    arr.push(&JsValue::from(1));
    arr.push(&JsValue::from(true));
    arr.push(&JsValue::from("hello"));

    let str1: String = JSON::stringify(&JsValue::from(arr)).unwrap().into();
    assert_eq!(str1, "[1,true,\"hello\"]");

    let obj = Object::new();
    Reflect::set(obj.as_ref(), &JsValue::from("foo"), &JsValue::from("bar")).unwrap();
    let str2: String = JSON::stringify(&JsValue::from(obj)).unwrap().into();
    assert_eq!(str2, "{\"foo\":\"bar\"}");
}

#[wasm_bindgen_test]
fn stringify_error() {
    let func = Function::new_no_args("throw new Error(\"rust really rocks\")");
    let obj = Object::new();
    Reflect::set(obj.as_ref(), &JsValue::from("toJSON"), func.as_ref()).unwrap();

    let result = JSON::stringify(&JsValue::from(obj));
    assert!(result.is_err());
    let err_obj = result.unwrap_err();
    assert!(err_obj.is_instance_of::<Error>());
    let err: &Error = err_obj.dyn_ref().unwrap();
    let err_msg: String = From::from(err.message());
    assert!(err_msg.contains("rust really rocks"));
}

#[wasm_bindgen_test]
fn stringify_with_replacer() {
    let obj = Object::new();
    Reflect::set(obj.as_ref(), &JsValue::from("foo"), &JsValue::from("bar")).unwrap();
    Reflect::set(
        obj.as_ref(),
        &JsValue::from("hello"),
        &JsValue::from("world"),
    )
    .unwrap();

    let replacer_array = Array::new();
    replacer_array.push(&JsValue::from("hello"));
    let output1: String =
        JSON::stringify_with_replacer(&JsValue::from(obj.clone()), &JsValue::from(replacer_array))
            .unwrap()
            .into();
    assert_eq!(output1, "{\"hello\":\"world\"}");

    let replacer_func =
        Function::new_no_args("return arguments[0] === 'hello' ? undefined : arguments[1]");
    let output2: String =
        JSON::stringify_with_replacer(&JsValue::from(obj), &JsValue::from(replacer_func))
            .unwrap()
            .into();
    assert_eq!(output2, "{\"foo\":\"bar\"}");
}

#[wasm_bindgen_test]
fn stringify_with_replacer_error() {
    let arr = Array::new();
    arr.push(&JsValue::from(1));
    arr.push(&JsValue::from(true));
    arr.push(&JsValue::from("hello"));

    let replacer = Function::new_no_args("throw new Error(\"rust really rocks\")");

    let result = JSON::stringify_with_replacer(&JsValue::from(arr), &JsValue::from(replacer));
    assert!(result.is_err());
    let err_obj = result.unwrap_err();
    assert!(err_obj.is_instance_of::<Error>());
    let err: &Error = err_obj.dyn_ref().unwrap();
    let err_msg: String = From::from(err.message());
    assert!(err_msg.contains("rust really rocks"));
}

#[wasm_bindgen_test]
fn stringify_with_replacer_and_space() {
    let arr = Array::new();
    arr.push(&JsValue::from(1));
    arr.push(&JsValue::from(true));
    arr.push(&JsValue::from("hello"));

    let output1: String = JSON::stringify_with_replacer_and_space(
        &JsValue::from(arr),
        &JsValue::NULL,
        &JsValue::from(4),
    )
    .unwrap()
    .into();
    assert_eq!(output1, "[\n    1,\n    true,\n    \"hello\"\n]");

    let obj = Object::new();
    Reflect::set(obj.as_ref(), &JsValue::from("foo"), &JsValue::from("bar")).unwrap();
    Reflect::set(
        obj.as_ref(),
        &JsValue::from("hello"),
        &JsValue::from("world"),
    )
    .unwrap();

    let replacer_array = Array::new();
    replacer_array.push(&JsValue::from("hello"));
    let output2: String = JSON::stringify_with_replacer_and_space(
        &JsValue::from(obj.clone()),
        &JsValue::from(replacer_array),
        &JsValue::from(4),
    )
    .unwrap()
    .into();
    assert_eq!(output2, "{\n    \"hello\": \"world\"\n}");

    let replacer_func =
        Function::new_no_args("return arguments[0] === 'hello' ? undefined : arguments[1]");
    let output3: String = JSON::stringify_with_replacer_and_space(
        &JsValue::from(obj),
        &JsValue::from(replacer_func),
        &JsValue::from(4),
    )
    .unwrap()
    .into();
    assert_eq!(output3, "{\n    \"foo\": \"bar\"\n}");
}

#[wasm_bindgen_test]
fn stringify_with_replacer_and_space_error() {
    let arr = Array::new();
    arr.push(&JsValue::from(1));
    arr.push(&JsValue::from(true));
    arr.push(&JsValue::from("hello"));

    let replacer = Function::new_no_args("throw new Error(\"rust really rocks\")");

    let result = JSON::stringify_with_replacer_and_space(
        &JsValue::from(arr),
        &JsValue::from(replacer),
        &JsValue::from(4),
    );
    assert!(result.is_err());
    let err_obj = result.unwrap_err();
    assert!(err_obj.is_instance_of::<Error>());
    let err: &Error = err_obj.dyn_ref().unwrap();
    let err_msg: String = From::from(err.message());
    assert!(err_msg.contains("rust really rocks"));
}

#[wasm_bindgen_test]
fn json_extends() {
    #[wasm_bindgen]
    extern "C" {
        #[wasm_bindgen(js_name = JSON)]
        static json: JSON;
    }

    assert!(json.is_instance_of::<Object>());
    let _: &Object = json.as_ref();
}