package TestCase::JSON {
  use SPVM::JSON;
  use SPVM::List;
  use SPVM::Int;
  use SPVM::Double;
  use SPVM::JSON::Bool;
  use SPVM::Math::BigInteger;
  use SPVM::Math::BigDecimal;

  our $DEFAULT_JSON : SPVM::JSON;

  BEGIN {
    $DEFAULT_JSON = SPVM::JSON->new;
    $DEFAULT_JSON->{canonical} = 1;
  }

  sub _validate : int ($json : SPVM::JSON, $input : string[], $expected : string[]) {
    for (my $i = 0; $i < @$input; $i++) {
      my $decoded = $json->decode($input->[$i]);
      my $actual = $json->encode($decoded);
      unless ($actual eq $expected->[$i]) {
        warn("input: " . $input->[$i] . "\nexpected: " . $expected->[$i] . "\nactual: $actual");
        return 0;
      }
    }
    return 1;
  }

  sub test_empty : int () {
    my $input = [
      # empty
      "",
      # spaces
      " ", "\n", "\t", "\r",
      # multiple spaces
      " \r\n\t  \r\r\t\t\n "
    ];
    my $expected = ["", "", "", "", "", ""];
    return _validate($DEFAULT_JSON, $input, $expected);
  }

  sub test_flat_hash : int () {
    my $tests = ["{}", "{\"digit\":42}", "{\"string\":\"vstr\"}", "{\"double\":0.123}", "{\"bool_true\":true}", "{\"bool_false\":false}", "{\"A\":0.1,\"ABC\":false,\"a\":1,\"qwerty\":\"asdfg\"}"];
    return _validate($DEFAULT_JSON, $tests, $tests);
  }

  sub test_flat_list : int () {
    my $tests = ["[]", "[1,2,3]", "[\"abc\",\"123\"]", "[123,\"abc\",0,\"123abc\",3.1415]"];
    return _validate($DEFAULT_JSON, $tests, $tests);
  }

  sub test_digits_int : int () {
    my $tests = ["[0,1,-1]"];
    return _validate($DEFAULT_JSON, $tests, $tests);
  }

  sub test_digits_long : int () {
    my $tests = ["[100000000000,-100000000000]"];
    my $json = SPVM::JSON->new;
    $json->{canonical} = 1;
    $json->{num_policy} = SPVM::JSON->NUM_ALLOW_LONG;
    return _validate($json, $tests, $tests);
  }

  sub test_digits_double : int () {
    my $input = [
      "0.123", "-0.123", "3.14", "-3.14", "123.987", "-123.987",
      "1.23456e+1", "1.23456e-1", "1.23456e+08", "1.23456e-08", "1.23456e+008", "1.23456e-008",
      "1.23456e+018", "1.23456e-018", "9.9e-100", "9.9e+300", "-1.23e+123"
    ];
    my $expected = [
      0.123, -0.123, 3.14, -3.14, 123.987, -123.987,
      1.23456e+1, 1.23456e-1, 1.23456e+08, 1.23456e-08, 1.23456e+008, 1.23456e-008,
      1.23456e+018, 1.23456e-018, 9.9e-100, 9.9e+300, -1.23e+123
    ];
    for (my $i = 0; $i < @$input; $i++) {
      my $got = (SPVM::Double)($DEFAULT_JSON->decode($input->[$i]));
      unless ($got->val == $expected->[$i]) {
        return 0;
      }
      my $re_got = (SPVM::Double)($DEFAULT_JSON->decode($DEFAULT_JSON->encode($got)));
      unless ($re_got->val == $expected->[$i]) {
        return 0;
      }
    }
    return 1;
  }

  sub test_digits_big_integer : int () {
    my $json = SPVM::JSON->new;
    $json->{num_policy} = SPVM::JSON->NUM_ALLOW_BIGNUM;
    my $test = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
    my $val = $json->decode($test);
    unless ($val isa SPVM::Math::BigInteger) {
      return 0;
    }
    my $got = (SPVM::Math::BigInteger)$val;
    unless ($got->to_str eq $test) {
      return 0;
    }
    return 1;
  }

  sub test_digits_big_decimal : int () {
    my $json = SPVM::JSON->new;
    $json->{num_policy} = SPVM::JSON->NUM_ALLOW_BIGNUM;
    my $inputs = [
      "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679",
      "123.45",
      "0.0123",
      "123.45e10",
      "123.45e-10",
      "0.0001e+10",
      "0.00010100000e-10",
    ];
    my $expected = [
      "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679",
      "1.2345e2",
      "1.23e-2",
      "1.2345e12",
      "1.2345e-8",
      "1e6",
      "1.01e-14",
    ];
    for (my $i = 0; $i < @$inputs; ++$i) {
      my $val = $json->decode($inputs->[$i]);
      unless ($val isa SPVM::Math::BigDecimal) {
        return 0;
      }
      my $got = (SPVM::Math::BigDecimal)
      $val;
      unless ($got->to_str eq $expected->[$i]) {
        warn("got: " . $got->to_str . "\nexpected: " . $expected->[$i]);
        return 0;
      }
    }
    return 1;
  }

  sub test_root_is_primitive : int () {
    my $tests = ["\"abc\"", "123", "true"];
    return _validate($DEFAULT_JSON, $tests, $tests);
  }

  sub test_nest_object : int () {
    my $tests = [
      "{\"A\":{\"B\":1,\"C\":{\"D\":0.1,\"E\":true,\"F\":\"str\",\"G\":[\"elem\",\"ents\",{\"key\":\"value\"}]}},\"end\":\"eof\"}"
    ];
    unless (_validate($DEFAULT_JSON, $tests, $tests)) {
      return 0;
    }
    return 1;
  }

  sub test_spaces : int () {
    my $inputs = [
      # Root type is "hash"
      " { \"key\" \n\t: 123\t,\n\t\t\"list\" :\n[\t1\t,\r2\t,\t3\n]}\r\r",
      # Root type is "list"
      " [ 1, 3.14\n, true \t , \"a\" ] " # primitives: Int, Double, Bool, string
    ];
    my $outputs = [
      "{\"key\":123,\"list\":[1,2,3]}",
      "[1,3.14,true,\"a\"]"
    ];
    return _validate($DEFAULT_JSON, $inputs, $outputs);
  }

  sub test_format_name_separator : int () {
    my $hash = SPVM::Hash->newa([(object) "key", "value"]);
    my $json = SPVM::JSON->new;
    $json->{space_before} = 0;
    $json->{space_after} = 0;
    unless ($json->encode($hash) eq "{\"key\":\"value\"}") {
      return 0;
    }
    $json->{space_before} = 1;
    $json->{space_after} = 0;
    unless ($json->encode($hash) eq "{\"key\" :\"value\"}") {
      return 0;
    }
    $json->{space_before} = 0;
    $json->{space_after} = 1;
    unless ($json->encode($hash) eq "{\"key\": \"value\"}") {
      return 0;
    }
    $json->{space_before} = 1;
    $json->{space_after} = 1;
    unless ($json->encode($hash) eq "{\"key\" : \"value\"}") {
      return 0;
    }
    return 1;
  }

  sub test_format_indent : int () {
    my $hash = SPVM::Hash->newa([(object) "a", "v1", "b", "v2", "c", SPVM::List->newa([(object) "x", "y"])]);
    my $json = SPVM::JSON->new;
    $json->{indent} = 1;
    $json->{indent_length} = 2;
    $json->{canonical} = 1;
    my $expected = "{\n  \"a\":\"v1\",\n  \"b\":\"v2\",\n  \"c\":[\n    \"x\",\n    \"y\"\n  ]\n}";
    my $actual = $json->encode($hash);
    unless ($expected eq $actual) {
      warn ("expected:\n$expected\nactual:\n$actual");
      return 0;
    }
    return 1;
  }

  sub test_format_pretty : int () {
    my $hash = SPVM::Hash->newa([(object) "a", "v1", "b", "v2", "c", SPVM::List->newa([(object) "x", "y"])]);
    my $json = SPVM::JSON->new;
    $json->{canonical} = 1;
    $json->pretty(1);
    my $expected = "{\n   \"a\" : \"v1\",\n   \"b\" : \"v2\",\n   \"c\" : [\n      \"x\",\n      \"y\"\n   ]\n}";
    my $actual = $json->encode($hash);
    unless ($expected eq $actual) {
      warn ("expected:\n$expected\nactual:\n$actual");
      return 0;
    }
    $json->pretty(0);
    $expected = "{\"a\":\"v1\",\"b\":\"v2\",\"c\":[\"x\",\"y\"]}";
    $actual = $json->encode($hash);
    unless ($expected eq $actual) {
      warn ("expected:\n$expected\nactual:\n$actual");
      return 0;
    }
    return 1;
  }

  sub test_relaxed : int () {
    my $inputs  = ["{\"k\":1,}","[1,]","{\"a\":1# comment\n,\"b\":2, ##\n\"c\":3,//xyz\n\"d\" /*multiple\ncomments//x#y*\nz*/:4}","\"\t\""];
    my $outputs = ["{\"k\":1}","[1]","{\"a\":1,\"b\":2,\"c\":3,\"d\":4}","\"\\t\""];
    my $json = SPVM::JSON->new;
    $json->{relaxed} = 1;
    unless (_validate($DEFAULT_JSON, $inputs, $outputs)) {
      return 0;
    }
    $json->{relaxed} = 0;
    for (my $i = 0; $i < @$inputs; $i++) {
      eval {
        $json->encode($json->decode($inputs->[$i]));
      };
      unless ($@) {
        return 0;
      }
      $@ = undef;
    }
    return 1;
  }

  sub test_special_chars : int () {
    my $tests = ["\"\\\"\\\\\\t\\n\\r\""];
    unless (_validate($DEFAULT_JSON, $tests, $tests)) {
      return 0;
    }
    return 1;
  }

  sub test_null : int () {
    unless ($DEFAULT_JSON->decode("null") == undef) {
      warn("decoded value must be undef if root value is null");
      return 0;
    }
    unless ($DEFAULT_JSON->encode(undef) eq "") {
      warn("encoded value must be empty is root value is undef");
      return 0;
    }
    unless ($DEFAULT_JSON->encode($DEFAULT_JSON->decode("{\"a\":null}")) eq "{\"a\":null}") {
      warn("value must remain null if it is not root");
      return 0;
    }
    return 1;
  }

  sub test_map_inf_nan_to_undef : int () {
    my $inputs = ["inf", "-inf", "nan"];
    for (my $i = 0; $i < @$inputs; ++$i) {
      unless ($DEFAULT_JSON->decode($inputs->[$i]) == undef) {
        return 0;
      }
    }
    return 1;
  }
}
