class TestCase::JSON {
  use JSON;
  use List;
  use Hash;
  use Fn;
  use Sort;
  use Point;

  static method _equals : int ($lhs : object, $rhs : object) {
    if ($lhs == undef) {
      unless ($rhs == undef) {
        return 0;
      }
    }
    elsif ($lhs isa Hash) {
      unless ($rhs isa Hash) {
        return 0;
      }

      my $l_hash = (Hash)$lhs;
      my $r_hash = (Hash)$lhs;

      my $l_keys = $l_hash->keys;
      my $r_keys = $r_hash->keys;

      if (@$l_keys == 0) {
        unless (@$r_keys == 0) {
          return 0;
        }
      }
      else {
        Sort->sort_string_asc($l_keys, 0, scalar @$l_keys);
        Sort->sort_string_asc($r_keys, 0, scalar @$r_keys);

        for (my $key_index = 0; $key_index < @$l_keys; ++$key_index) {
          my $key = $l_keys->[$key_index];
          unless ($l_keys->[$key_index] eq $r_keys->[$key_index]) {
            return 0;
          }
          unless (&_equals($l_hash->get($key), $r_hash->get($key))) {
            return 0;
          }
        }
      }
    }
    elsif ($lhs isa List) {
      unless ($rhs isa List) {
        return 0;
      }

      my $l_list = (List)$lhs;
      my $r_list = (List)$rhs;

      unless ($l_list->length == $r_list->length) {
        return 0;
      }

      my $length = $l_list->length;
      for (my $list_index = 0; $list_index < $length; ++$list_index) {
        unless (&_equals($l_list->get($list_index), $r_list->get($list_index))) {
          return 0;
        }
      }
    }
    elsif ($lhs isa string) {
      unless ($rhs isa string) {
        return 0;
      }

      my $l_str = (string)$lhs;
      my $r_str = (string)$rhs;

      unless ($l_str eq $r_str) {
        return 0;
      }
    }
    elsif ($lhs isa Int) {
      unless ($rhs isa Int) {
        return 0;
      }

      my $l_int = (Int)$lhs;
      my $r_int = (Int)$rhs;

      unless ($l_int->value == $r_int->value) {
        return 0;
      }
    }
    elsif ($lhs isa Bool) {
      unless ($rhs isa Bool) {
        return 0;
      }

      my $l_bool = (Bool)$lhs;
      my $r_bool = (Bool)$rhs;

      unless ($l_bool->value == $r_bool->value) {
        return 0;
      }
    }
    elsif ($lhs isa Double) {
      unless ($rhs isa Double) {
        return 0;
      }

      my $l_dbl = (Double)$lhs;
      my $r_dbl = (Double)$rhs;

      unless ($l_dbl->value == $r_dbl->value) {
        return 0;
      }
    }
    else {
      die "Not implemented type is used in \$lhs";
    }

    return 1;
  }

  static method encode_json_hash : int () {
    my $json = JSON->new;
    
    {
      my $input    = Hash->new({});
      my $got      = $json->encode($input);
      my $expected = "{}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({"int" => 42 });
      my $got      = $json->encode($input);
      my $expected = "{\"int\":42}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({"string" => "vstr"});
      my $got      = $json->encode($input);
      my $expected = "{\"string\":\"vstr\"}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({"double" => 0.123});
      my $got      = $json->encode($input);
      my $expected = "{\"double\":0.123}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({"bool_true" => true});
      my $got      = $json->encode($input);
      my $expected = "{\"bool_true\":true}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({"bool_false" => false});
      my $got      = $json->encode($input);
      my $expected = "{\"bool_false\":false}";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = Hash->new({
        "bool"   => false,
        "double" => 0.1,
        "int"    => 1,
        "str"    => "hoge",
      });
      my $got      = $json->encode($input);
      my $expected = "{\"bool\":false,\"double\":0.1,\"int\":1,\"str\":\"hoge\"}";
      unless ($got eq $expected) {
        return 0;
      }
    }

    {
      my $input = Hash->new({
        "A" => Hash->new({
          "B" => 1,
          "C" => Hash->new({
            "D" => 0.1,
            "E" => true,
            "F" => "str",
            "G" => List->new([(object)
              "abc",
              123,
              Hash->new({
                "H" => "val",
            }),
            ]),
          })
        }),
        "I" => "EOF",
      });
      my $got = $json->encode($input);
      my $expected = "{\"A\":{\"B\":1,\"C\":{\"D\":0.1,\"E\":true,\"F\":\"str\",\"G\":[\"abc\",123,{\"H\":\"val\"}]}},\"I\":\"EOF\"}";
      unless ($got eq $expected) {
        return 0;
      }
      return 1;
    }
    
    return 1;
  }
  
  static method encode_json_object : int () {
    my $json = JSON->new;
    
    {
      my $input = Point->new(1, 2);
      eval { $json->encode($input); };
      unless (Fn->contains($@, "The \$spvm_data contains a value of an invalid type")) {
        return 0;
      }
      
    }
    
    return 1;
  }
  
  static method decode_json_hash : int () {
    my $json = JSON->new;
    
    {
      my $input    = "{}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "{\"string\":\"vstr\"}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({"string" => "vstr"});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "{\"double\":0.123}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({"double" => 0.123});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "{\"bool_true\":true}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({"bool_true" => true});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "{\"bool_false\":false}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({"bool_false" => false});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "{\"double\":0.1,\"bool\":false,\"int\":1,\"str\":\"hoge\"}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({
        "bool"   => false,
        "double" => 0.1,
        "str"    => "hoge",
      });
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = " { \"key\" \n\t: 123\t,\n\t\t\"list\" :\n[\t1\t,\r2\t,\t3\n]}\r\r";
      my $got      = $json->decode($input);
      my $expected = Hash->new({
        "key"  => 123.0,
        "list" => List->new([(object) 1.0, 2.0, 3.0])
      });
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    
    # Duplicated keys - overwritten the first value
    {
      my $input    = "{\"foo\":\"a\", \"foo\":\"b\"}";
      my $got      = $json->decode($input);
      my $expected = Hash->new({"foo" => "b"});
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    
    {
      my $input = "{\"A\":{\"B\":1,\"C\":{\"D\":0.1,\"E\":true,\"F\":\"str\",\"G\":[\"abc\",123,{\"H\":\"val\"}]}},\"I\":\"EOF\"}";
      my $got = $json->decode($input);
      my $expected = Hash->new({
        "A" => Hash->new({
          "B" => 1.0,
          "C" => Hash->new({
            "D" => 0.1,
            "E" => true,
            "F" => "str",
            "G" => List->new([(object)
              "abc",
              123.0,
              Hash->new({
                "H" => "val",
              }),
            ]),
          })
        }),
        "I" => "EOF",
      });
      unless (&_equals($got, $expected)) {
        return 0;
      }
      return 1;
    }
    
    return 1;
  }

  static method encode_json_list : int () {
    my $json = JSON->new;
    
    {
      my $input    = List->new([]);
      my $got      = $json->encode($input);
      my $expected = "[]";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = List->new([(object) 1, 2, 3]);
      my $got      = $json->encode($input);
      my $expected = "[1,2,3]";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = List->new([(object) "abc", "def"]);
      my $got      = $json->encode($input);
      my $expected = "[\"abc\",\"def\"]";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input    = List->new([(object) 0, 3.14, "abc", true]);
      my $got      = $json->encode($input);
      my $expected = "[0,3.14,\"abc\",true]";
      unless ($got eq $expected) {
        return 0;
      }
    }
    return 1;
  }

  static method decode_json_list : int () {
    my $json = JSON->new;
    
    {
      my $input    = "[]";
      my $got      = $json->decode($input);
      my $expected = List->new([]);
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "[3,2,1]";
      my $got      = $json->decode($input);
      my $expected = List->new([(object) 3.0, 2.0, 1.0]);
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "[\"def\",\"abc\"]";
      my $got      = $json->decode($input);
      my $expected = List->new([(object) "def", "abc"]);
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = "[0,3.14,\"abc\",true]";
      my $got      = $json->decode($input);
      my $expected = List->new([(object) 0.0, 3.14, "abc", true]);
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input    = " [ 3.14\n, true \t , \"a\" ] ";
      my $got      = $json->decode($input);
      my $expected = List->new([(object) 3.14, true, "a"]);
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    return 1;
  }

  static method encode_json_number : int () {
    my $json = JSON->new;
    
    my $test_case  = [
      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 < @$test_case; ++$i) {
      my $input    = Double->new($test_case->[$i]);
      my $got      = $json->encode($input);
      my $expected = (string)$test_case->[$i];
      warn "[Test Output]Got:$got,Expected:$expected";
      unless ($got eq $expected) {
        return 0;
      }
    }

    # Byte
    {
      my $input    = Fn->BYTE_MIN;
      my $got      = $json->encode($input);
      my $expected = "-128";
      unless ($got eq $expected) {
        return 0;
      }
    }
    
    # Short
    {
      my $input    = Fn->SHORT_MIN;
      my $got      = $json->encode($input);
      my $expected = "-32768";
      unless ($got eq $expected) {
        return 0;
      }
    }
    
    # Int
    {
      my $input    = Fn->INT_MIN;
      my $got      = $json->encode($input);
      my $expected = "-2147483648";
      unless ($got eq $expected) {
        return 0;
      }
    }
    
    # Long
    {
      my $input    = Fn->LONG_MIN;
      my $got      = $json->encode($input);
      my $expected = "-9223372036854775808";
      unless ($got eq $expected) {
        return 0;
      }
    }
    
    # Exceptions
    {
      # Float - Infinity
      {
        my $input = Math->INFINITYF;
        eval { $json->encode($input); }
        unless (Fn->contains($@, "The \$spvm_data cannot contain an inifinity float value. The found depth is 0.")) {
          return 0;
        }
      }
      
      # Float - NaN
      {
        my $input = Math->NANF;
        eval { $json->encode($input); }
        unless (Fn->contains($@, "The \$spvm_data cannot contain a NaN float value. The found depth is 0.")) {
          return 0;
        }
      }
      
      # Double - Infinity
      {
        my $input = Math->INFINITY;
        eval { $json->encode($input); }
        unless (Fn->contains($@, "The \$spvm_data cannot contain an inifinity double value. The found depth is 0.")) {
          return 0;
        }
      }
      
      # Double - NaN
      {
        my $input = Math->NAN;
        eval { $json->encode($input); }
        unless (Fn->contains($@, "The \$spvm_data cannot contain a NaN double value. The found depth is 0.")) {
          return 0;
        }
      }

      # Invalid Type
      {
        my $input = Point->new;
        eval { $json->encode($input); }
        unless (Fn->contains($@, "The \$spvm_data contains a value of an invalid type. The type is Point. The found depth is 0.")) {
          return 0;
        }
      }
    }
    
    return 1;
  }

  static method decode_json_number : int () {
    my $json = JSON->new;
    
    my $test_case  = [
      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 < @$test_case; ++$i) {
      my $input    = "" . $test_case->[$i];
      my $got      = "" . ((Double)$json->decode($input))->value;
      my $expected = "" . $test_case->[$i];
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      eval {
        $json->decode("-");
      };
      unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
        return 0;
      }
    }
    {
      my $test_case = ["1e.", "1e+.", "1.."];
      for (my $i = 0; $i < @$test_case; ++$i) {
        my $input = $test_case->[$i];
        eval {
          $json->decode($input);
        };
        unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
          return 0;
        }
      }
    }
    {
      my $test_case = ["1.e", "1e+e", "1ee"];
      for (my $i = 0; $i < @$test_case; ++$i) {
        my $input = $test_case->[$i];
        eval {
          $json->decode($input);
        };
        unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
          return 0;
        }
      }
    }
    {
      my $test_case = ["1.}"];
      for (my $i = 0; $i < @$test_case; ++$i) {
        my $input = $test_case->[$i];
        eval {
          $json->decode($input);
        };
        unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
          return 0;
        }
      }
    }
    {
      my $test_case = ["1e}"];
      for (my $i = 0; $i < @$test_case; ++$i) {
        my $input = $test_case->[$i];
        eval {
          $json->decode($input);
        };
        unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
          return 0;
        }
      }
    }
    {
      my $test_case = ["1e+}", "1e-}"];
      for (my $i = 0; $i < @$test_case; ++$i) {
        my $input = $test_case->[$i];
        eval {
          $json->decode($input);
        };
        unless ($@ && Fn->index($@, "Invalid number", 0) >= 0) {
          return 0;
        }
      }
    }
    return 1;
  }

  static method encode_json_bool : int () {
    my $json = JSON->new;
    
    {
      my $input = true;
      my $got = $json->encode($input);
      my $expected = "true";
      unless ($got eq $expected) {
        return 0;
      }
    }
    {
      my $input = false;
      my $got = $json->encode($input);
      my $expected = "false";
      unless ($got eq $expected) {
        return 0;
      }
    }
    return 1;
  }

  static method decode_json_bool : int () {
    my $json = JSON->new;
    
    {
      my $input = "true";
      my $got = $json->decode($input);
      my $expected = true;
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    {
      my $input = "false";
      my $got = $json->decode($input);
      my $expected = false;
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    return 1;
  }

  static method encode_json_string : int () {
    my $json = JSON->new;
    
    {
      my $input = "str";
      my $got = $json->encode($input);
      my $expected = "\"str\"";
      unless ($got eq $expected) {
        return 0;
      }
    }
    # special characters
    # '"'
    {
      my $input = "\"";
      my $got = $json->encode($input);
      my $expected = "\"\\\"\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    
    # '/'
    {
      my $input = "/";
      my $got = $json->encode($input);
      my $expected = "\"\\/\"";
      
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    
    # '\'
    {
      my $input = "\\";
      my $got = $json->encode($input);
      my $expected = "\"\\\\\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\n'
    {
      my $input = "\n";
      my $got = $json->encode($input);
      my $expected = "\"\\n\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\r'
    {
      my $input = "\r";
      my $got = $json->encode($input);
      my $expected = "\"\\r\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\t'
    {
      my $input = "\t";
      my $got = $json->encode($input);
      my $expected = "\"\\t\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    
    return 1;
  }

  static method decode_json_string : int () {
    my $json = JSON->new;
    
    {
      my $input = "\"str\"";
      my $got = $json->decode($input);
      my $expected = "str";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # special characters
    # '"'
    {
      my $input = "\"\\\"\"";
      my $got = $json->decode($input);
      my $expected = "\"";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\'
    {
      my $input = "\"\\\\\"";
      my $got = $json->decode($input);
      my $expected = "\\";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\/'
    {
      my $input = "\"\\/\"";
      my $got = $json->decode($input);
      my $expected = "/";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }

    # '/'
    {
      my $input = "\"/\"";
      my $got = $json->decode($input);
      my $expected = "/";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }

    # TODO
    # '\b'
    {
      my $input = "\"\b\"";
      my $got = $json->decode($input);
      my $expected = "\x08";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }

    # '\f'
    {
      my $input = "\"\\f\"";
      my $got = $json->decode($input);
      my $expected = "\f";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\n'
    {
      my $input = "\"\\n\"";
      my $got = $json->decode($input);
      my $expected = "\n";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\r'
    {
      my $input = "\"\\r\"";
      my $got = $json->decode($input);
      my $expected = "\r";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }
    # '\t'
    {
      my $input = "\"\\t\"";
      my $got = $json->decode($input);
      my $expected = "\t";
      unless (&_equals($got, $expected)) {
        return 0;
      }
    }

    # [TODO]
    # control characters are not allowed
    {
      for (my $ch : byte = 0; $ch <= 31; ++$ch) {
        eval {
          $json->decode("\"" . (string)[(byte)$ch] . "\"");
        };
        unless ($@ && Fn->index($@, "Invalid string", 0) >= 0) {
          return 0;
        }
      }
      eval {
        $json->decode("\"" . (string)[(byte)127 ] . "\"");
      };
      unless ($@ && Fn->index($@, "Invalid string", 0) >= 0) {
        return 0;
      }
    }

    return 1;
  }

  static method encode_json_null : int () {
    my $json = JSON->new;
    
    unless ($json->encode(undef) eq "null") {
      return 0;
    }
    return 1;
  }
  
  static method decode_json_null : int () {
    my $json = JSON->new;
    
    unless ($json->decode("null") == undef) {
      return 0;
    }
    return 1;
  }

  static method decode_json_invalid_json_data : int () {
    my $json = JSON->new;
    
    {
      my $input    = "";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "The decoding of the \$json_data failed. Unexpected character \"\" at line 1 column 1.")) {
        return 0;
      }
    }
    
    {
      my $input    = "ppp";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "The decoding of the \$json_data failed. Unexpected character \"p\" at line 1 column 1.")) {
        return 0;
      }
    }
    
    {
      my $input    = " ppp";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "The decoding of the \$json_data failed. Unexpected character \"p\" at line 1 column 2.")) {
        return 0;
      }
    }
    
    {
      my $input    = "\n[ppp]";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "The decoding of the \$json_data failed. Unexpected character \"p\" at line 2 column 2.")) {
        return 0;
      }
    }
    
    {
      my $input    = "[";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "Unexpected character \"\" at line 1 column 2.")) {
        return 0;
      }
    }
    
    {
      my $input    = "{";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "Unexpected character \"\" at line 1 column 2.")) {
        return 0;
      }
    }
    
    {
      my $input    = "to";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "Expected token: \"true\" at line 1 column 1.")) {
        return 0;
      }
    }

    {
      my $input    = "fo";
      eval { $json->decode($input); }
      
      unless (Fn->contains($@, "Expected token: \"false\" at line 1 column 1.")) {
        return 0;
      }
    }

    return 1;
  }
}