package TestCase::Lib::SPVM::Hash {
  use SPVM::Int;
  use SPVM::Hash;
  use SPVM::Comparator;
  use SPVM::List;
  use SPVM::Util;
  use SPVM::Sort;

  sub test_set_get_numeric : int () {

    my $hash = SPVM::Hash->new;

    # set_byte, get_byte
    {
      $hash->set_byte(foo => SPVM::Util->INT8_MAX());
      unless ($hash->get("foo") isa SPVM::Byte) {
        return 0;
      }
      my $value = $hash->get_byte("foo");

      unless ($value == SPVM::Util->INT8_MAX()) {
        return 0;
      }
    }

    # set_short, get_short
    {
      $hash->set_short(foo => SPVM::Util->INT16_MAX());
      unless ($hash->get("foo") isa SPVM::Short) {
        return 0;
      }
      my $value = $hash->get_short("foo");

      unless ($value == SPVM::Util->INT16_MAX()) {
        return 0;
      }
    }

    # set_int, get_int
    {
      $hash->set_int(foo => SPVM::Util->INT32_MAX());
      unless ($hash->get("foo") isa SPVM::Int) {
        return 0;
      }
      my $value = $hash->get_int("foo");

      unless ($value == SPVM::Util->INT32_MAX()) {
        return 0;
      }
    }

    # set_string, get_string
    {
      my $str = "bar";
      $hash->set_string(foo => $str);
      unless ($hash->get_string("foo") isa string) {
        return 0;
      }
      my $value = $hash->get_string("foo");

      unless ($value == $str) {
        return 0;
      }
    }

    # set_long, get_long
    {
      $hash->set_long(foo => SPVM::Util->INT64_MAX());
      unless ($hash->get("foo") isa SPVM::Long) {
        return 0;
      }
      my $value = $hash->get_long("foo");

      unless ($value == SPVM::Util->INT64_MAX()) {
        return 0;
      }
    }

    # set_float, get_float
    {
      $hash->set_float(foo => SPVM::Util->FLT_MIN());
      unless ($hash->get("foo") isa SPVM::Float) {
        return 0;
      }
      my $value = $hash->get_float("foo");

      unless ($value == SPVM::Util->FLT_MIN()) {
        return 0;
      }
    }

    # set_double, get_double
    {
      $hash->set_double(foo => SPVM::Util->DBL_MIN());
      unless ($hash->get("foo") isa SPVM::Double) {
        return 0;
      }
      my $value = $hash->get_double("foo");

      unless ($value == SPVM::Util->DBL_MIN()) {
        return 0;
      }
    }

    return 1;
  }

  sub test_murmur_hash : int () {
    my $seed = 123456789;
    # Testcase is created from https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/hash_bytes.cc#L72-L112
    # Change size_t to uint32_t to use 32bit integer.
    # Compile the program by the following command:
    # g++ -std=c++2a -D__SIZEOF_SIZE_T__=4 hash_bytes.cc
    my $strings = ["a", "<>", "ABC", "1234", "asdfg", "zxcvbn", "1qazxsw", "3edcvfr4", "1234567890-=\\][poiuytrewqasdfghjkl;'"];
    my $hashes = [846967266L, 200612280L, 4178773334L, 1870759112L, 61159236L, 623182920L, 1738266155L, 123403562L, 4243681504L];
    for (my $i = 0; $i < @$strings; ++$i) {
      unless (SPVM::Hash->_murmur_hash($strings->[$i], $seed) == $hashes->[$i]) {
        return 0;
      }
    }
    return 1;
  }

  sub test_set : int () {
    my $keys = ["alice", "bob", "carol", "1234567890-="];
    my $vals = [SPVM::Int->new(1), SPVM::Int->new(2), undef, SPVM::Int->new(3)];
    my $hash = SPVM::Hash->new;
    for (my $i = 0; $i < @$keys; ++$i) {
      $hash->set($keys->[$i], $vals->[$i]);
    }
    unless ($hash->count == @$keys) {
      return 0;
    }
    for (my $i = 0; $i < @$keys; ++$i) {
      unless ($hash->get($keys->[$i]) == $vals->[$i]) {
        return 0;
      }
    }
    return 1;
  }

  sub test_set_do_not_refer_caller_key : int () {
    my $hash = SPVM::Hash->new;
    my $key = "a";
    $hash->set($key, SPVM::Int->new(1));
    $key = "b";
    unless ($hash->exists("a")) {
      return 0;
    }
    unless ($hash->exists("b") == 0) {
      return 0;
    }
    return 1;
  }

  sub test_get : int () {
    my $hash = SPVM::Hash->new;
    $hash->set_int("a" => 1);
    $hash->set("b" => undef);
    $hash->set("c" => "str");

    unless (((SPVM::Int)($hash->get("a")))->val == 1) {
      return 0;
    }
    unless ($hash->get("b") == undef) {
      return 0;
    }
    unless (((string)$hash->get("c")) eq "str") {
      return 0;
    }
    unless ($hash->get("no_key") == undef) {
      return 0;
    }
    return 1;
  }

  sub test_exists : int () {
    my $keys = ["alice", "bob"];
    my $vals = [SPVM::Int->new(1), SPVM::Int->new(2)];
    my $hash = SPVM::Hash->new;
    for (my $i = 0; $i < @$keys; ++$i) {
      $hash->set($keys->[$i], $vals->[$i]);
    }
    for (my $i = 0; $i < @$keys; ++$i) {
      unless ($hash->exists($keys->[$i])) {
        return 0;
      }
    }
    unless ($hash->exists("carol") == 0) {
      return 0;
    }
    return 1;
  }

  sub test_delete_with_no_hash_collision : int () {
    my $hash = SPVM::Hash->new;
    $hash->set("alice", SPVM::Int->new(1));
    $hash->set("bob", SPVM::Int->new(2));
    unless (((SPVM::Int)($hash->delete("alice")))->val == 1) {
      return 0;
    }
    unless ($hash->exists("alice") == 0) {
      return 0;
    }
    unless ($hash->count == 1) {
      return 0;
    }
    unless ($hash->delete("alice") == undef) {
      return 0;
    }
    unless ($hash->delete("carol") == undef) {
      return 0;
    }
    unless ($hash->count == 1) {
      return 0;
    }
    unless ($hash->exists("bob") == 1) {
      return 0;
    }
    return 1;
  }

  sub test_delete_with_hash_collision : int () {
    my $hash = SPVM::Hash->new;
    $hash->set("alice", SPVM::Int->new(1));
    $hash->set("bob", SPVM::Int->new(2));
    unless (((SPVM::Int)$hash->delete("alice"))->val == 1) {
      return 0;
    }
    unless ($hash->exists("alice") == 0) {
      return 0;
    }
    unless ($hash->count == 1) {
      return 0;
    }
    unless ($hash->delete("alice") == undef) {
      return 0;
    }
    unless ($hash->count == 1) {
      return 0;
    }

    unless ($hash->exists("bob") == 1) {
      return 0;
    }
    unless (((SPVM::Int)($hash->delete("bob")))->val == 2) {
      return 0;
    }
    unless ($hash->count == 0) {
      return 0;
    }
    unless ($hash->exists("bob") == 0) {
      return 0;
    }
    return 1;
  }

  sub test_rehash : int () {
    my $initial_capacity = 16;
    my $hash = SPVM::Hash->new;
    for (my $i = 0; $i < 14; ++$i) {
      my $key = "key" . $i;
      $hash->set_int($key => $i);
    }

    unless ($hash->_bucket_count == $initial_capacity * 2) {
      return 0;
    }
    unless (@{$hash->_entries} == $initial_capacity * 2) {
      return 0;
    }

    unless ($hash->get_int("key12") == 12) {
      return 0;
    }

    return 1;
  }

  sub test_keys : int () {
    my $hash = SPVM::Hash->new;
    my $key0 = "a";
    my $key1 = "b";
    my $key2 = "c";

    $hash->set_int($key0 => 1);
    $hash->set_int($key1 => 2);
    $hash->set_int($key2 => 3);

    my $keys = $hash->keys;

    unless (@$keys == 3) {
      return 0;
    }

    SPVM::Sort->sortstr($keys);

    unless ($keys->[0] == $key0) {
      return 0;
    }

    unless ($keys->[1] == $key1) {
      return 0;
    }

    unless ($keys->[2] == $key2) {
      return 0;
    }

    return 1;
  }

  sub test_values : int () {
    my $hash = SPVM::Hash->new;
    my $key0 = "a";
    my $key1 = "b";
    my $key2 = "c";

    $hash->set_int($key0 => 1);
    $hash->set_int($key1 => 2);
    $hash->set_int($key2 => 3);

    my $values = $hash->values;

    unless (@$values == 3) {
      return 0;
    }

    my $int_values = new int[3];
    $int_values->[0] = (int)$values->[0];
    $int_values->[1] = (int)$values->[1];
    $int_values->[2] = (int)$values->[2];

    SPVM::Sort->sorti($int_values);

    unless ($int_values->[0] == 1) {
      return 0;
    }

    unless ($int_values->[1] == 2) {
      return 0;
    }

    unless ($int_values->[2] == 3) {
      return 0;
    }

    return 1;
  }

  sub test_copy : int () {
    my $hash = SPVM::Hash->new;
    for (my $i = 0; $i < 3; ++$i) {
      $hash->set("key$i", $i);
    }
    my $copied = $hash->copy;
    for (my $i = 0; $i < 3; ++$i) {
      unless (((SPVM::Int)$copied->get("key$i"))->val == $i) {
        return 0;
      }
    }
    $copied->set("extra_key", 123);
    if ($hash->exists("extra_keys")) {
      return 0;
    }
    unless ($copied->exists("extra_key")) {
      return 0;
    }
    return 1;
  }
}