package TestCase::Lib::SPVM::List {
  use SPVM::Int;
  use SPVM::List;
  use SPVM::ArrayUtil (equals_array_object);
  use SPVM::EqualityChecker::SameObject;
  
  sub new : int () {
    # new with undef
    {
      my $list = SPVM::List->new(undef);
      
      unless ($list->length == 0) {
        return 0;
      }
      
      $list->push(SPVM::Int->new(1));
      unless ((int)$list->get(0) == 1) {
        return 0;
      }
    }
    
    return 1;
  }
  
  sub insert : int () {
    # Insert to first
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";
      
      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->insert(0 => $x4);
      unless (equals_array_object($list->to_array, [$x4, $x1, $x2, $x3], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
    }

    # Insert
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";
      
      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->insert(2 => $x4);
      unless (equals_array_object($list->to_array, [$x1, $x2, $x4, $x3], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
    }

    # Insert to last
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";
      
      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->insert(3 => $x4);
      unless (equals_array_object($list->to_array, [$x1, $x2, $x3, $x4], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
    }

    # Extend
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";

      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->insert(0 => $x4);
    }
    
    # Exception - insert to -1
    eval {
      my $list = SPVM::List->new([(object)"1", "2", "3"]);
      $list->insert(-1 => "2");
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;

    # Exception - insert to length + 1
    eval {
      my $list = SPVM::List->new([(object)"1", "2", "3"]);
      $list->insert(4 => "2");
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;
    
    return 1;
  }

  sub remove : int () {
    # Remove
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";
      
      my $list = SPVM::List->new([(object)$x1, $x2, $x3, $x4]);
      my $value = $list->remove(1);
      unless (equals_array_object($list->to_array, [$x1, $x3, $x4], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
      unless ($value == $x2) {
        return 0;
      }
    }

    # Remove last
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";
      
      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->remove(2);
      unless (equals_array_object($list->to_array, [$x1, $x2], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
    }

    # Remove first
    {
      my $x1 = "1";
      my $x2 = "2";
      my $x3 = "3";
      my $x4 = "4";

      my $list = SPVM::List->new([(object)$x1, $x2, $x3]);
      $list->remove(0);
      unless (equals_array_object($list->to_array, [$x2, $x3], SPVM::EqualityChecker::SameObject->new)) {
        return 0;
      }
    }

    # Exception - remove to -1
    eval {
      my $list = SPVM::List->new([(object)"1", "2", "3"]);
      $list->remove(-1);
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;

    # Exception - remove to length
    eval {
      my $list = SPVM::List->new([(object)"1", "2", "3"]);
      $list->remove(3);
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;
    
    return 1;
  }

  sub equals_list_deeply : int ($got : SPVM::List, $expected : SPVM::List) {
    my $all_match = 1;
    for (my $i = 0; $i < $expected->length; $i++) {
      unless ($got->get($i) == $expected->get($i)) {
        $all_match = 0;
      }
    }
    
    if ($all_match) {
      return 1;
    }
    else {
      return 0;
    }
  }

  sub equals_array : int ($got : oarray, $expected : oarray) {
    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. methodscript " . $i);
        return 0;
      }
    }
    return 1;
  }

  sub length : int () {
    if (SPVM::List->new_len([], 0)->length != 0) {
      return 0;
    }
    if (SPVM::List->new([SPVM::Int->new(1)])->length != 1) {
      return 0;
    }
    return 1;
  }

  sub push : int () {
    my $list = SPVM::List->new_len(new SPVM::Int[0], 0);

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

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

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

    return 1;
  }

  sub pop : int () {
    my $list = SPVM::List->new([SPVM::Int->new(1), undef, SPVM::Int->new(2)]);
    unless (((SPVM::Int)$list->pop)->value == 2) {
      return 0;
    }
    unless ($list->pop == undef) {
      return 0;
    }
    unless (((SPVM::Int)$list->pop)->value == 1) {
      return 0;
    }
    unless (equals_list_deeply($list, SPVM::List->new_len([], 0))) {
      return 0;
    }
    eval {
      $list->pop;
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;
    
    return 1;
  }

  sub unshift : int () {
    my $list = SPVM::List->new_len(new SPVM::Int[0], 1);

    my $v1 = SPVM::Int->new(1);
    $list->unshift($v1);

    my $v2 = SPVM::Int->new(2);
    $list->unshift($v2);

    my $v3 = SPVM::Int->new(3);
    $list->unshift($v3);

    my $v4 = SPVM::Int->new(4);
    $list->unshift($v4);

    my $v5 = SPVM::Int->new(5);
    $list->unshift($v5);

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

    return 1;
  }

  sub shift : int () {
    my $list = SPVM::List->new([SPVM::Int->new(1), undef, SPVM::Int->new(2)]);
    unless (((SPVM::Int)$list->shift)->value == 1) {
      return 0;
    }
    unless ($list->shift == undef) {
      return 0;
    }
    unless (((SPVM::Int)$list->shift)->value == 2) {
      return 0;
    }
    eval {
      $list->shift;
    };
    unless ($@) {
      return 0;
    }
    $@ = undef;
    
    unless (equals_list_deeply($list, SPVM::List->new(new object[0]))) {
      return 0;
    }
    return 1;
  }

  sub offset_by_alternate_push_and_shift : int () {
    my $list = SPVM::List->new_len(new SPVM::Int[0], 4);
    for (my $i = 0; $i < 16; $i++) {
      $list->push(1);
      $list->shift;
    }
    return 1;
  }

  sub offset_by_alternate_unshift_and_pop : int () {
    my $list = SPVM::List->new_len(new SPVM::Int[0], 4);
    for (my $i = 0; $i < 16; $i++) {
      $list->unshift(1);
      $list->pop;
    }
    return 1;
  }

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

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

  sub set_array : int () {
    # Set array
    {
      my $list = SPVM::List->new(["1", "2", "3"]);
      $list->set_array(["3", "4", "5"]);
      unless (SPVM::ArrayUtil->equals_array_string((string[])$list->to_array, ["3", "4", "5"])) {
        return 0;
      }
    }
    
    # Exception - Array must be defined
    {
      my $list = SPVM::List->new(["1", "2", "3"]);
      eval { $list->set_array(undef); };
      unless ($@) {
        return 0;
      }
    }

    # Exception - The length of argument array must be same as the length of current list array
    {
      my $list = SPVM::List->new(["1", "2", "3"]);
      eval { $list->set_array(["1", "2", "3", "4"]); };
      unless ($@) {
        return 0;
      }
    }
    
    $@ = undef;
    
    return 1;
  }

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

  sub to_array : int () {
    {
      my $list = SPVM::List->new([(object)"abc", 1, 3.14]);
      my $objects = $list->to_array;

      unless ($objects isa object[]) {
        return 0;
      }

      unless ((string)($objects->[0]) eq "abc" &&
          ((SPVM::Int)$objects->[1])->value == 1 &&
          ((SPVM::Double)$objects->[2])->value == 3.14) {
        return 0;
      }
    }

    {
      my $list = SPVM::List->new([SPVM::Int->new(1), SPVM::Int->new(2)]);
      my $objects = (SPVM::Int[])$list->to_array;

      unless ($objects isa SPVM::Int[]) {
        return 0;
      }

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

      eval { (SPVM::Long[])$list->to_array; };
      unless ($@) {
        return 0;
      }
    }
    
    $@ = undef;

    return 1;
  }

  sub resize : int () {
    my $a = "a";
    my $b = "b";
    my $c = "c";

    # 3 to 3
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      $list->resize(3);
      unless ($list->length == 3) {
        return 0;
      }
      unless ($list->get(0) == $a && $list->get(1) == $b && $list->get(2) == $c) {
        return 0;
      }
    }
    
    # 3 to 0
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      $list->resize(0);
      unless ($list->length == 0) {
        return 0;
      }
    }

    
    # 3 to 4
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      $list->resize(4);
      unless ($list->length == 4) {
        return 0;
      }
      unless ($list->get(3) == undef && $list->get(0) == $a && $list->get(1) == $b && $list->get(2) == $c) {
        return 0;
      }
    }
    
    # 3 to 32(over capacity)
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      $list->resize(32);
      unless ($list->length == 32) {
        return 0;
      }
      unless ($list->get(3) == undef && $list->get(31) == undef) {
        return 0;
      }
    }

    # 3 to 2, 2 to 3 again
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      $list->resize(2);
      unless ($list->length == 2) {
        return 0;
      }
      $list->resize(3);
      unless ($list->length == 3) {
        return 0;
      }
      unless ($list->get(2) == undef) {
        return 0;
      }
    }
    
    # Exception - New length must be more than or equals to 0
    {
      my $list = SPVM::List->new([]);
      $list->push($a);
      $list->push($b);
      $list->push($c);
      eval { $list->resize(-1); };
      unless ($@) {
        return 0;
      }
    }
    
    $@ = undef;
    return 1;
  }

}