package TestCase::List {
  use SPVM::Int;
  use SPVM::List;

  sub check_fields : int ($list : SPVM::List, $capacity : int, $offset : int, $length : int) {
    # check_fields fields
    unless ($list->_capacity == $capacity && $list->_offset == $offset && $list->length == $length) {
      warn("List fields mismatch.\n\t" .
          "got:      (capacity: " . $list->_capacity . ", offset: " . $list->_offset . ", length: " . $list->length . ")\n\t" .
          "expected: (capacity: " . $capacity . ", offset: " . $offset . ", length: " . $length . ")");
      return 0;
    }
    return 1;
  }

  sub equals_list_deeply : int ($got : SPVM::List, $expected : SPVM::List) {
    unless ($got->length == $expected->length) {
      warn("Length mismatch. Try check_fields() first");
      return 0;
    }
    for (my $i = 0; $i < $expected->length; $i++) {
      my $got_index = $i + $got->_offset;
      if ($got_index < 0) {
        $got_index += $got->_capacity;
      }
      my $expected_index = $i + $expected->_offset;
      if ($expected_index < 0) {
        $expected_index += $expected->_capacity;
      }
      unless ($got->_values->[$got_index] == $expected->_values->[$expected_index]) {
        warn("List mismatch.");
        print("\tgot:      ");
        for (my $j = 0; $j < $got->length; $j++) {
          if ($got->get($j) == undef) {
            print("(undef) ");
          }
          else {
            print(((SPVM::Int)$got->get($j))->val . " ");
          }
        }
        print("\n");
        print("\texpected: ");
        for (my $j = 0; $j < $expected->length; $j++) {
          if ($expected->get($j) == undef) {
            print("(undef) ");
          }
          else {
            print(((SPVM::Int)$expected->get($j))->val . " ");
          }
        }
        print("\n");
        return 0;
      }
    }
    return 1;
  }

  sub equals_array : int ($got : object [], $expected : object []) {
    if (@$got != @$expected) {
      warn("Array size mismatch. got: " . @$got . ", expected: " . @$expected);
      return 0;
    }
    for (my $i = 0; $i < @$expected; $i++) {
      if ($got->[$i] != $expected->[$i]) {
        warn("Array content mismatch. subscript " . $i);
        return 0;
      }
    }
    return 1;
  }

  sub test_ctor_default : int () {
    unless (check_fields(SPVM::List->new, 16, -16, 0)) {
      return 0;
    }
    unless (equals_array(SPVM::List->new->_values, new object[16])) {
      return 0;
    }
    return 1;
  }

  sub test_ctor_with_capacity : int () {
    unless (check_fields(SPVM::List->new_with_capacity(20000), 20000, -20000, 0)) {
      return 0;
    }
    unless (equals_array(SPVM::List->new_with_capacity(20000)->_values, new object[20000])) {
      return 0;
    }
    eval {
      SPVM::List->new_with_capacity(0);
    }
    unless ($@) {
      return 0;
    }
    $@ = undef;
    eval {
      SPVM::List->new_with_capacity(-1);
    }
    unless ($@) {
      return 0;
    }
    $@ = undef;
    return 1;
  }

  sub test_ctor_with_array : int () {
    my $list = SPVM::List->new_with_array(new object[0]);
    unless (check_fields($list, 0, 0, 0)) {
      return 0;
    }
    unless (equals_array($list->_values, new object[0])) {
      return 0;
    }

    $list = SPVM::List->new_with_array(new object[1]);
    unless (check_fields($list, 1, -1, 1)) {
      return 0;
    }
    unless (equals_array($list->_values, new object[1])) {
      return 0;
    }

    my $v1 = SPVM::Int->new(1);
    my $vals = [(object) $v1];
    $list = SPVM::List->new_with_array($vals);
    unless (check_fields($list, 1, -1, 1)) {
      return 0;
    }
    unless (equals_array($list->_values, $vals)) {
      return 0;
    }

    my $v2 = SPVM::Int->new(2);
    $vals = new object[4];
    $vals->[1] = $v1;
    $vals->[3] = $v2;
    $list = SPVM::List->new_with_array($vals);
    unless (check_fields($list, 4, -4, 4)) {
      return 0;
    }
    unless (equals_array($list->_values, $vals)) {
      return 0;
    }

    return 1;
  }

  sub test_length : int () {
    if (SPVM::List->new->length != 0) {
      return 0;
    }
    if (SPVM::List->new_with_array([(object) 1])->length != 1) {
      return 0;
    }
    return 1;
  }

  sub test_push : int () {
    my $list = SPVM::List->new_with_capacity(1);

    my $v1 = SPVM::Int->new(1);
    $list->push($v1);
    unless (check_fields($list, 1, -1, 1)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1]))) {
      return 0;
    }

    my $v2 = SPVM::Int->new(2);
    $list->push($v2);
    unless (check_fields($list, 2, -2, 2)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2]))) {
      return 0;
    }

    my $v3 = SPVM::Int->new(3);
    $list->push($v3);
    unless (check_fields($list, 4, -4, 3)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2, $v3]))) {
      return 0;
    }

    # no reallocation
    my $v4 = SPVM::Int->new(3);
    $list->push($v4);
    unless (check_fields($list, 4, -4, 4)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2, $v3, $v4]))) {
      return 0;
    }

    return 1;
  }

  sub test_pop : int () {
    my $list = SPVM::List->new_with_array([(object) SPVM::Int->new(1), undef, SPVM::Int->new(2)]);
    unless (((SPVM::Int)$list->pop)->val == 2) {
      return 0;
    }
    unless ($list->pop == undef) {
      return 0;
    }
    unless (((SPVM::Int)$list->pop)->val == 1) {
      return 0;
    }
    unless (check_fields($list, 3, -3, 0)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new)) {
      return 0;
    }
    unless (equals_array($list->_values, new object[3])) {
      return 0;
    }
    unless ($list->pop == undef) { # empty list returns undef
      return 0;
    }
    return 1;
  }

  sub test_unshift : int () {
    my $list = SPVM::List->new_with_capacity(1);

    my $v1 = SPVM::Int->new(1);
    $list->unshift($v1);
    unless (check_fields($list, 1, -1, 1)) {
      return 0;
    }

    my $v2 = SPVM::Int->new(2);
    $list->unshift($v2);
    unless (check_fields($list, 2, -1, 2)) {
      return 0;
    }

    my $v3 = SPVM::Int->new(3);
    $list->unshift($v3);
    unless (check_fields($list, 4, -1, 3)) {
      return 0;
    }

    my $v4 = SPVM::Int->new(4);
    $list->unshift($v4);
    unless (check_fields($list, 4, -2, 4)) {
      return 0;
    }

    my $v5 = SPVM::Int->new(5);
    $list->unshift($v5);
    unless (check_fields($list, 8, -1, 5)) {
      return 0;
    }

    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v5, $v4, $v3, $v2, $v1]))) {
      return 0;
    }

    return 1;
  }

  sub test_shift : int () {
    my $list = SPVM::List->new_with_array([(object) SPVM::Int->new(1), undef, SPVM::Int->new(2)]);
    unless (((SPVM::Int)$list->shift)->val == 1) {
      return 0;
    }
    unless ($list->shift == undef) {
      return 0;
    }
    unless (((SPVM::Int)$list->shift)->val == 2) {
      return 0;
    }
    unless (check_fields($list, 3, -3, 0)) {
      return 0;
    }
    unless ($list->shift == undef) { # empty list returns undef
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array(new object[0]))) {
      return 0;
    }
    return 1;
  }

  sub test_offset_by_alternate_push_and_shift : int () {
    my $list = SPVM::List->new_with_capacity(4);
    for (my $i = 0; $i < 16; $i++) {
      $list->push(1);
      $list->shift;
      unless (check_fields($list, 4, ($i + 1) % 4 - 4, 0)) {
        return 0;
      }
    }
    return 1;
  }

  sub test_offset_by_alternate_unshift_and_pop : int () {
    my $list = SPVM::List->new_with_capacity(4);
    for (my $i = 0; $i < 16; $i++) {
      $list->unshift(1);
      $list->pop;
      unless (check_fields($list, 4, (3 - $i + 16) % 4 - 4, 0)) {
        return 0;
      }
    }
    return 1;
  }

  sub test_set : int () {
    my $list = SPVM::List->new_with_array([(object) SPVM::Int->new(1), undef]);

    my $v1 = SPVM::Int->new(2);
    $list->set(0, undef);
    $list->set(1, $v1);
    unless (check_fields($list, 2, -2, 2)) {
      return 0;
    }
    my $expected = new object[2];
    $expected->[1] = $v1;
    unless (equals_list_deeply($list, SPVM::List->new_with_array($expected))) {
      return 0;
    }

    return 1;
  }

  sub test_get : int () {
    my $list = SPVM::List->new_with_array([(object) SPVM::Int->new(1), undef]);
    unless (((SPVM::Int)$list->get(0))->val == 1) {
      return 0;
    }
    unless ($list->get(1) == undef) {
      return 0;
    }
    return 1;
  }

  sub test_splice : int () {
    my $v1 = SPVM::Int->new(1);
    my $v2 = SPVM::Int->new(2);
    my $v3 = SPVM::Int->new(3);
    my $v4 = SPVM::Int->new(4);
    my $v5 = SPVM::Int->new(5);
    my $vi1 = SPVM::Int->new(10);
    my $vi2 = SPVM::Int->new(11);
    my $replace = [(object) $vi1, $vi2];
    # extract left-inside
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2, $v3, $v4, $v5]);
      my $extracted = $list->splice(1, 2, undef);
      unless (equals_array($extracted, [(object) $v2, $v3])) {
        return 0;
      }
      unless (check_fields($list, 5, -5, 3)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v4, $v5]))) {
        return 0;
      }
    }
    # extract right-inside
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2, $v3, $v4, $v5]);
      my $extracted = $list->splice(2, 2, undef);
      unless (equals_array($extracted, [(object) $v3, $v4])) {
        return 0;
      }
      unless (check_fields($list, 5, -5, 3)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2, $v5]))) {
        return 0;
      }
    }
    # replace list at left corner
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(0, 1, $replace);
      unless (equals_array($extracted, [(object) $v1])) {
        return 0;
      }
      unless (check_fields($list, 3, -3, 3)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $vi1, $vi2, $v2]))) {
        return 0;
      }
    }
    # replace list at right corner
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(1, 1, $replace);
      unless (equals_array($extracted, [(object) $v2])) {
        return 0;
      }
      unless (check_fields($list, 3, -3, 3)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $vi1, $vi2]))) {
        return 0;
      }
    }
    # replace list at center
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2, $v3, $v4, $v5]);
      my $extracted = $list->splice(1, 3, $replace);
      unless (equals_array($extracted, [(object) $v2, $v3, $v4])) {
        return 0;
      }
      unless (check_fields($list, 5, -5, 4)) {
        return 0;
      }
    }
    # insert list back with cut_length = 0
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(2, 0, $replace);
      unless (equals_array($extracted, new object[0])) {
        return 0;
      }
      unless (check_fields($list, 4, -4, 4)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2, $vi1, $vi2]))) {
        return 0;
      }
    }
    # insert list back with cut_length > 0
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(2, 10, $replace);
      unless (equals_array($extracted, new object[0])) {
        return 0;
      }
      unless (check_fields($list, 4, -4, 4)) {
        return 0;
      }
    }
    return 1;
  }

  sub test_splice_no_reallocation : int () {
    my $v1 = SPVM::Int->new(1);
    my $v2 = SPVM::Int->new(2);
    my $vi1 = SPVM::Int->new(10);
    my $replace = [(object) $vi1];
    # extract left corner
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(0, 1, undef);
      unless (equals_array($extracted, [(object) $v1])) {
        return 0;
      }
      unless (check_fields($list, 2, -2, 1)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v2]))) {
        return 0;
      }
    }
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(0, 1, $replace);
      unless (equals_array($extracted, [(object) $v1])) {
        return 0;
      }
      unless (check_fields($list, 2, -2, 2)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $vi1, $v2]))) {
        return 0;
      }
    }
    # extract right corner
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(1, 1, undef);
      unless (equals_array($extracted, [(object) $v2])) {
        return 0;
      }
      unless (check_fields($list, 2, -2, 1)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1]))) {
        return 0;
      }
    }
    {
      my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
      my $extracted = $list->splice(1, 1, $replace);
      unless (equals_array($extracted, [(object) $v2])) {
        return 0;
      }
      unless (check_fields($list, 2, -2, 2)) {
        return 0;
      }
      unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $vi1]))) {
        return 0;
      }
    }
    # cut_length = replace_length = 0
    my $list = SPVM::List->new_with_array([(object) $v1, $v2]);
    my $extracted = $list->splice(2, 0, undef);
    unless (equals_array($extracted, new object[0])) {
      return 0;
    }
    unless (check_fields($list, 2, -2, 2)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v1, $v2]))) {
      return 0;
    }
    return 1;
  }

  sub test_splice_offset_is_valid_when_removed : int () {
    my $list = SPVM::List->new_with_capacity(2);
    my $v1 = SPVM::Int->new(1);
    my $v2 = SPVM::Int->new(2);
    $list->unshift($v1);
    $list->push($v2);
    my $extracted = $list->splice(0, 1, undef);
    unless (equals_array($extracted, [(object) $v1])) {
      return 0;
    }
    unless (check_fields($list, 2, -1, 1)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $v2]))) {
      return 0;
    }
    return 1;
  }

  sub test_splice_offset_is_valid_when_appended : int () {
    my $list = SPVM::List->new_with_capacity(3);
    my $v1 = SPVM::Int->new(1);
    my $v2 = SPVM::Int->new(2);
    my $vi1 = SPVM::Int->new(3);
    my $vi2 = SPVM::Int->new(3);
    $list->unshift($v1);
    $list->push($v2);
    my $extracted = $list->splice(0, 1, [(object) $vi1, $vi2]);
    unless (equals_array($extracted, [(object) $v1])) {
      return 0;
    }
    unless (check_fields($list, 3, -1, 3)) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_with_array([(object) $vi1, $vi2, $v2]))) {
      return 0;
    }
    return 1;
  }

  sub test_splice_deallocation : int () {
    my $list = SPVM::List->new_with_array([(object) SPVM::Int->new(1), SPVM::Int->new(2)]);
    $list->splice(1, 1, undef);
    unless ($list->_values->[1] == undef) {
      return 0;
    }
    return 1;
  }
}
