package SPVM::JSON {
  use SPVM::Hash;
  use SPVM::List;
  use SPVM::Int;
  use SPVM::Long;
  use SPVM::Double;
  use SPVM::JSON::Bool;
  use SPVM::Math::BigDecimal;
  use SPVM::Math::BigInteger;
  use SPVM::Math::StringToNumber;

  has indent : public int;
  has indent_length : public int;
  has space_before : public int;
  has space_after : public int;
  has canonical : public int;
  has relaxed : public int;
  has num_policy : public int;

  enum {
    NUM_DEFAULT,
    NUM_ALLOW_LONG,
    NUM_ALLOW_BIGNUM,
  }

  sub _die_with_dump_remain : void ($die_message : string, $s : string, $start : int, $end : int) {
    die $die_message . "\nRemains... '" . (string)(sliceb((byte [])$s, $start, $end - $start)) . "'";
  }

  sub _skip_comment : void ($s : string, $i : int&, $end : int) {
    my $comment = 0;
    for (; $$i < $end ; ++$$i) {
      if ($comment == 1 && $s->[$$i] == '\n') {
        return;
      }
      if ($comment == 2) {
        unless ($s->[$$i] == '*') {
          next;
        }
        ++$$i;
        unless ($$i < $end) {
          return; # no */ is allowed
        }
        if ($s->[$$i] == '/') {
          $comment = 0;
        }
      }
      elsif ($s->[$$i] == '#') {
        $comment = 1;
      }
      elsif ($s->[$$i] == '/') {
        ++$$i;
        unless ($$i < $end) {
          _die_with_dump_remain("Unexpected token '/'", $s, $$i, $end);
        }
        if ($s->[$$i] == '/') {
          $comment = 1;
        }
        elsif ($s->[$$i] == '*') {
          $comment = 2;
        }
        else {
          _die_with_dump_remain("Unexpected token '/'", $s, $$i, $end);
        }
      }
      elsif ($comment == 0) {
        return;
      }
    }
  }

  sub _skip_spaces : void ($self : self, $s : string, $i : int&, $end : int) {
    for (; 1 ; ++$$i) {
      if ($self->{relaxed}) {
        _skip_comment($s, $i, $end);
      }
      if ($$i == $end) {
        return;
      }
      unless ($s->[$$i] == ' ' || $s->[$$i] == '\n' || $s->[$$i] == '\t' || $s->[$$i] == '\r') {
        last;
      }
    }
  }

  sub _skip_spaces_at_not_end : void ($self : self, $s : string, $i : int&, $end : int) {
    $self->_skip_spaces($s, $i, $end);
    if ($$i == $end) {
      _die_with_dump_remain("Incomplete JSON", $s, $$i, $end);
    }
  }

  sub _expect_token : void ($s : string, $i : int&, $end : int, $expected : string) {
    my $length = length $expected;
    unless ($$i + $length <= $end) {
      _die_with_dump_remain("Expected token: $expected doesn't exist", $s, $$i, $end);
    }
    my $begin = $$i;
    while (1) {
      unless ($s->[$$i] == $expected->[$$i - $begin]) {
        _die_with_dump_remain("Expected token: $expected doesn't exist", $s, $$i, $end);
      }
      ++$$i;
      if ($$i - $begin == $length) {
        last;
      }
    }
  }

  sub _decode_string : string ($self : self, $s : string, $i : int&, $end : int) {
    my $chars = SPVM::List->new;
    _expect_token($s, $i, $end, "\"");
    my $special_char_expected = 0;
    while (1) {
      if ($$i >= $end || (!$special_char_expected && $s->[$$i] == '"')) {
        last;
      }
      my $got_char = $s->[$$i];
      if ($special_char_expected) {
        switch ((int)$got_char) {
          case 34: # '"'
          case 92: # '\\'
            $chars->push(SPVM::Int->new($got_char));
            last;
          case 116: # 't'
          $chars->push(SPVM::Int->new('\t'));
            last;
          case 110: # 'n'
            $chars->push(SPVM::Int->new('\n'));
            last;
          case 114: # 'r'
            $chars->push(SPVM::Int->new('\r'));
            last;
          default:
            _die_with_dump_remain("Undefined special char", $s, $$i, $end);
        }
        $special_char_expected = 0;
      }
      elsif ($got_char == '\\') {
        $special_char_expected = 1;
      }
      else {
        if (!$self->{relaxed} && $got_char == '\t') {
          _die_with_dump_remain("Literal ASCII tab characters are not allowed in string", $s, $$i, $end);
        }
        $chars->push(SPVM::Int->new($got_char));
      }
      ++$$i;
    }
    if ($$i == $end) {
      _die_with_dump_remain("Invalid string. end-quote doesn't exist", $s, $$i, $end);
    }
    _expect_token($s, $i, $end, "\"");
    my $chars_length = $chars->length;
    my $value = new byte [$chars_length];
    for (my $k = 0; $k < $chars_length; ++$k) {
      my $got = ((SPVM::Int)$chars->shift)->val;
      $value->[$k] = (byte)$got;
    }
    return (string)$value;
  }

  sub _decode_digits : object ($self : self, $s : string, $i : int&, $end : int) {
    my $end_of_digits = -1;
    my $is_double = 0;
    if ($self->{num_policy} == NUM_ALLOW_BIGNUM()) {
      my $begin = $$i;
      for (; $$i < $end; ++$$i) {
        if ($s->[$$i] == 'e' || $s->[$$i] == 'E' || $s->[$$i] == '.') {
          $is_double = 1;
        }
        else {
          unless (('0' <= $s->[$$i] && $s->[$$i] <= '9') ||
              $s->[$$i] == '+' || $s->[$$i] == '-') {
            $end_of_digits = $$i;
            last;
          }
        }
      }
      if ($end_of_digits == -1) {
        $end_of_digits = $end;
      }
      my $num = sliceb((byte [])$s, $begin, $end_of_digits - $begin);
      if ($is_double) {
        return SPVM::Math::BigDecimal->new($num);
      }
      else {
        return SPVM::Math::BigInteger->new($num);
      }
    }

    my $digits : long = 0;
    my $offset = 0;
    my $has_digit = 0;
    my $has_sign = 0;
    my $decimal_stat = 0; # 0: '.' hasn't appeared yet. 1: '.' or 'e' has already appeared. 2: value is "0" or next char should be '.'
    if ($s->[$$i] == '-') {
      ++$$i;
      $has_sign = 1;
    }
    if ($s->[$$i] == '0') {
      ++$$i;
      $has_digit = 1;
      $decimal_stat = 2;
    }
    while ($$i < $end) {
      if ($s->[$$i] == '.') {
        if ($decimal_stat == 1) {
          _die_with_dump_remain("Invalid digits. Dot should appear at most once", $s, $$i, $end);
        }
        unless ($has_digit) {
          _die_with_dump_remain("Invalid digits. Not found digits before dot", $s, $$i, $end);
        }
        $decimal_stat = 1;
        ++$$i;
        unless ($$i < $end && '0' <= $s->[$$i] && $s->[$$i] <= '9') {
          _die_with_dump_remain("Invalid digits. Not found digits after dot", $s, $$i, $end);
        }
        $digits *= 10;
        $digits += $s->[$$i++] - '0';
        ++$offset;
      }
      elsif ($decimal_stat == 2) {
        # value is "0"
        last;
      }
      elsif ('0' <= $s->[$$i] && $s->[$$i] <= '9') {
        $has_digit = 1;
        if ($decimal_stat == 1) {
          $digits *= 10;
          $digits += $s->[$$i++] - '0';
          ++$offset;
        }
        else {
          $digits *= 10;
          $digits += $s->[$$i++] - '0';
        }
      }
      elsif ($s->[$$i] == 'e' || $s->[$$i] == 'E') {
        ++$$i;
        $decimal_stat = 1;
        unless ($$i < $end) {
          _die_with_dump_remain("Invalid digits. digits must exist after e/E", $s, $$i, $end);
        }
        my $side : int; # -1: "e+XX", +1: "e-XX"
        if ($s->[$$i] == '+') {
          ++$$i;
          $side = -1;
        }
        elsif ($s->[$$i] == '-') {
          ++$$i;
          $side = +1;
        }
        unless ($$i < $end && '0' <= $s->[$$i] && $s->[$$i] <= '9') {
          _die_with_dump_remain("Invalid digits. digits must exist after e/E", $s, $$i, $end);
        }
        my $exp = $s->[$$i++] - '0';
        while ($$i < $end && '0' <= $s->[$$i] && $s->[$$i] <= '9') {
          $exp *= 10;
          $exp += $s->[$$i++] - '0';
        }
        $offset += $side * $exp;
      }
      else {
        # digits has decoded.
        last;
      }
    }

    unless ($has_digit) {
      _die_with_dump_remain("Invalid digits. Not found digits after '-'", $s, $$i, $end);
    }
    if ($decimal_stat == 1) {
      my $value = (double) $digits / pow(10, $offset);
      if ($has_sign) {
        $value *= -1;
      }
      return SPVM::Double->new($value);
    }
    elsif ($self->{num_policy} == NUM_DEFAULT()) {
      my $value = (int)$digits;
      if ($has_sign) {
        $value *= -1;
      }
      return SPVM::Int->new($value);
    }
    elsif ($self->{num_policy} == NUM_ALLOW_LONG()) {
      my $value = $digits;
      if ($has_sign) {
        $value *= -1;
      }
      return SPVM::Long->new($value);
    }
    else {
      die "Unexpected num_policy: " . $self->{num_policy};
    }
  }

  sub _decode_true : SPVM::JSON::Bool ($self : self, $s : string, $i : int&, $end : int) {
    _expect_token($s, $i, $end, "true");
    return SPVM::JSON::Bool->TRUE;
  }

  sub _decode_false : SPVM::JSON::Bool ($self : self, $s : string, $i : int&, $end : int) {
    _expect_token($s, $i, $end, "false");
    return SPVM::JSON::Bool->FALSE;
  }

  sub _decode_hash : SPVM::Hash ($self : self, $s : string, $i : int&, $end : int) {
    my $hash = SPVM::Hash->new;
    my $has_element = 0;
    _expect_token($s, $i, $end, "{");
    while (1) {
      # end of hash
      $self->_skip_spaces_at_not_end($s, $i, $end);
      if ($s->[$$i] == '}') {
        last;
      }

      # comma
      if ($has_element) {
        _expect_token($s, $i, $end, ",");
        $self->_skip_spaces_at_not_end($s, $i, $end);
        # allow last comma
        if ($self->{relaxed} && $s->[$$i] == '}') {
          last;
        }
      }
      else {
        $has_element = 1;
      }

      # key
      my $key = $self->_decode_string($s, $i, $end);

      # separator
      $self->_skip_spaces_at_not_end($s, $i, $end);
      _expect_token($s, $i, $end, ":");

      # value
      $self->_skip_spaces_at_not_end($s, $i, $end);
      $hash->set($key, $self->_decode_value($s, $i, $end));
    }
    _expect_token($s, $i, $end, "}");
    return $hash;
  }

  sub _decode_list : SPVM::List ($self : self, $s : string, $i : int&, $end : int) {
    _expect_token($s, $i, $end, "[");
    my $list = SPVM::List->new;
    my $has_element = 0;
    while (1) {
      # end of list
      $self->_skip_spaces_at_not_end($s, $i, $end);
      if ($s->[$$i] == ']') {
        last;
      }

      # comma
      if ($has_element) {
        _expect_token($s, $i, $end, ",");
        $self->_skip_spaces_at_not_end($s, $i, $end);
        # allow last comma
        if ($self->{relaxed} && $s->[$$i] == ']') {
          last;
        }
      }
      else {
        $has_element = 1;
      }
      $list->push($self->_decode_value($s, $i, $end));
    }
    _expect_token($s, $i, $end, "]");
    return $list;
  }

  sub _decode_value : object ($self : self, $s : string, $i : int&, $end : int) {
    $self->_skip_spaces_at_not_end($s, $i, $end);
    # undef mapped values (prefix match)
    if ($s->[$$i] == 'n') {
      if ($$i + 2 < $end && $s->[$$i + 1] == 'a' && $s->[$$i + 2] == 'n') {
        $$i += 3;
        return undef;
      }
      elsif ($$i + 3 < $end && $s->[$$i + 1] == 'u' && $s->[$$i + 2] == 'l' && $s->[$$i + 3] == 'l') {
        $$i += 4;
        return undef;
      }
    }
    elsif ($$i + 2 < $end && $s->[$$i] == 'i' && $s->[$$i + 1] == 'n' && $s->[$$i + 2] == 'f') {
      $$i += 3;
      return undef;
    }
    elsif ($$i + 3 < $end && $s->[$$i] == '-' && $s->[$$i + 1] == 'i' && $s->[$$i + 2] == 'n' && $s->[$$i + 3] == 'f') {
      $$i += 4;
      return undef;
    }

    # objects
    elsif ($s->[$$i] == '{') {
      return $self->_decode_hash($s, $i, $end);
    }
    elsif ($s->[$$i] == '[') {
      return $self->_decode_list($s, $i, $end);
    }

    # primitives
    elsif ($s->[$$i] == '"') {
      return $self->_decode_string($s, $i, $end);
    }
    elsif ('0' <= $s->[$$i] && $s->[$$i] <= '9' || $s->[$$i] == '-') {
      return $self->_decode_digits($s, $i, $end);
    }
    elsif ($s->[$$i] == 't') {
      return $self->_decode_true($s, $i, $end);
    }
    elsif ($s->[$$i] == 'f') {
      return $self->_decode_false($s, $i, $end);
    }

    # unknown token
    else {
      _die_with_dump_remain("Unexpected token.", $s, $$i, $end);
    }
  }

  sub _make_indent : string ($self : self, $depth : int) {
    unless ($depth > 0 && $self->{indent} && $self->{indent_length} > 0) {
      return "";
    }
    my $sum_length = $self->{indent_length} * $depth;
    my $indent_bytes = new byte [$sum_length];
    for (my $i = 0; $i < $sum_length; ++$i) {
      $indent_bytes->[$i] = ' ';
    }
    return (string)$indent_bytes;
  }

  # TODO: Use separated sort function from this module
  sub _sort_keys : void ($keys : string[]) {
    for (my $i = 0; $i < @$keys - 1; ++$i) {
      for (my $j = @$keys - 1; $j > $i; $j--) {
        if ($keys->[$j - 1] gt $keys->[$j]) {
          my $temp = $keys->[$j - 1];
          $keys->[$j - 1] = $keys->[$j];
          $keys->[$j] = $temp;
        }
      }
    }
  }

  sub _escape_string : string ($string : string) {
    my $length = length $string;
    my $chars = SPVM::List->new;
    for (my $i = 0; $i < $length; ++$i) {
      my $got_char = (int)($string->[$i]);
      my $special = -1;
      switch ($got_char) {
        case 9: # '\t'
          # Note: Decoded char from literal ASCII tab will be encoded with "\\t" (non-reversible).
          $special = 't';
          last;
        case 10: # '\n'
          $special = 'n';
          last;
        case 13: # '\r'
          $special = 'r';
          last;
        case 34: # '"'
          $special = '"';
          last;
        case 92: # '\\'
          $special = '\\';
          last;
        default: # FIXME: segmentation fault
      }
      if ($special == -1) {
        $chars->push(SPVM::Int->new($got_char));
      }
      else {
        $chars->push(SPVM::Int->new('\\'));
        $chars->push(SPVM::Int->new($special));
      }
    }
    my $chars_length = $chars->length;
    my $escaped = new byte [$chars_length];
    for (my $k = 0; $k < $chars_length; ++$k) {
      my $got = ((SPVM::Int)$chars->shift)->val;
      $escaped->[$k] = (byte)$got;
    }
    return (string)$escaped;
  }

  sub _encode_value : string ($self : self, $o : object, $depth : int) {
    if ($o == undef) {
      # null/inf/-inf/nan
      return "null";
    }
    # objects
    elsif ($o isa SPVM::Hash) {
      my $text = "";
      if ($self->{indent}) {
        $text .= $self->_make_indent($depth);
      }
      $text .= "{";
      if ($self->{indent}) {
        $text .= "\n";
      }
      my $hash = (SPVM::Hash)$o;
      my $keys = $hash->keys;
      if ($self->{canonical}) {
        _sort_keys($keys); # TODO: Use separated sort function from this module
      }
      for (my $i = 0; $i < @$keys; ++$i) {
        if ($i > 0) {
          $text .= ",";
          if ($self->{indent}) {
            $text .= "\n";
          }
        }
        if ($self->{indent}) {
          $text .= $self->_make_indent($depth + 1);
        }
        $text .= "\"" . $keys->[$i] . "\"";
        if ($self->{space_before}) {
          $text .= " ";
        }
        $text .= ":";
        if ($self->{space_after}) {
          $text .= " ";
        }
        unless ($hash->exists($keys->[$i])) {
          die "Unexpected error. Not found key: " . $keys->[$i];
        }
        $text .= $self->_encode_value($hash->get($keys->[$i]), $depth + 1);
      }
      if ($self->{indent}) {
        $text .= "\n";
        $text .= $self->_make_indent($depth);
      }
      $text .= "}";
      return $text;
    }
    elsif ($o isa SPVM::List) {
      my $text = "";
      $text .= "[";
      if ($self->{indent}) {
        $text .= "\n";
      }
      my $list = (SPVM::List)$o;
      my $length = $list->length;
      for (my $i = 0; $i < $length; ++$i) {
        if ($i > 0) {
          $text .= ",";
          if ($self->{indent}) {
            $text .= "\n";
          }
        }
        if ($self->{indent}) {
          $text .= $self->_make_indent($depth + 1);
        }
        $text .= $self->_encode_value($list->get($i), $depth + 1);
      }
      if ($self->{indent}) {
        $text .= "\n";
        $text .= $self->_make_indent($depth);
      }
      $text .= "]";
      return $text;
    }
    # primitives
    elsif ($o isa string) {
      my $escaped = _escape_string((string)$o);
      return "\"" . $escaped . "\"";
    }
    elsif ($o isa SPVM::Int) {
      return (string)(((SPVM::Int)$o)->val);
    }
    elsif ($o isa SPVM::Long) {
      return (string)(((SPVM::Long)$o)->val);
    }
    elsif ($o isa SPVM::Double) {
      return (string)(((SPVM::Double)$o)->val);
    }
    elsif ($o isa SPVM::Math::BigInteger) {
      return ((SPVM::Math::BigInteger)$o)->to_str;
    }
    elsif ($o isa SPVM::Math::BigDecimal) {
      return ((SPVM::Math::BigDecimal)$o)->to_str;
    }
    elsif ($o isa SPVM::JSON::Bool) {
      if (((SPVM::JSON::Bool)$o)->val) {
        return "true";
      }
      else {
        return "false";
      }
    }
    else {
      die "Unknown object type";
    }
  }

  sub _field_check : void ($self : self) {
    unless ($self->{num_policy} == NUM_DEFAULT() ||
        $self->{num_policy} == NUM_ALLOW_LONG() ||
        $self->{num_policy} == NUM_ALLOW_BIGNUM()) {
      die "Not allowed value is set to num_policy field";
    }
  }

  sub new : SPVM::JSON () {
    my $json = new SPVM::JSON;
    $json->{indent} = 0;
    $json->{indent_length} = 0;
    $json->{space_before} = 0;
    $json->{space_after} = 0;
    $json->{relaxed} = 1;
    return $json;
  }

  sub encode : string ($self : self, $object : object) {
    $self->_field_check;
    unless ($object) {
      return "";
    }
    return $self->_encode_value($object, 0);
  }

  sub decode : object ($self : self, $json_text : string) {
    $self->_field_check;
    my $length = length $json_text;
    my $iter = 0;
    $self->_skip_spaces($json_text, \$iter, $length);
    if ($iter == $length) {
      return undef;
    }
    my $ret = $self->_decode_value($json_text, \$iter, $length);
    $self->_skip_spaces($json_text, \$iter, $length);
    unless ($iter == $length) {
      _die_with_dump_remain("Not all json_text is decoded yet", $json_text, $iter, $length);
    }
    return $ret;
  }

  sub pretty : void ($self : self, $pretty : int) {
    if ($pretty) {
      $self->{indent} = 1;
      $self->{indent_length} = 3;
      $self->{space_before} = 1;
      $self->{space_after} = 1;
    }
    else {
      $self->{indent} = 0;
      $self->{indent_length} = 0;
      $self->{space_before} = 0;
      $self->{space_after} = 0;
    }
  }
}
