class TestCase::Interface {
  use TestCase::Minimal;
  use TestCase::Simple;
  use Stringable;
  use Cloneable;
  use List;
  use Point;
  use TestCase::Interface::Point;
  use TestCase::PointForTest;
  use TestCase::Interface::AnyObjectReturn;
  
  interface TestCase::Interface::Type;
  
  static method new : TestCase::Interface () {
    return new TestCase::Interface;
  }
  
  static method basic : int () {
    
    # Stringable minimal
    {
      my $minimal = TestCase::Minimal->new;
      $minimal->set_x(1);
      $minimal->set_y(2);
      
      my $stringable = (Stringable)$minimal;
      unless ($stringable->to_string eq "(1,2)") {
        return 0;
      }
    }
    
    # Stringable minimal - from object
    {
      my $minimal = TestCase::Minimal->new;
      $minimal->set_x(1);
      $minimal->set_y(2);
      
      my $stringable = (Stringable)(object)$minimal;
      unless ($stringable->to_string eq "(1,2)") {
        return 0;
      }
    }
    
    # Stringable simple
    {
      my $simple = TestCase::Simple->new;
      $simple->set_x(1);
      $simple->set_y(2);
      
      my $stringable = (Stringable)$simple;
      unless ($stringable->to_string eq "(1,2):Simple") {
        return 0;
      }
    }

    # Stringable simple - from object
    {
      my $simple = TestCase::Simple->new;
      $simple->set_x(1);
      $simple->set_y(2);
      
      my $stringable = (Stringable)(object)$simple;
      unless ($stringable->to_string eq "(1,2):Simple") {
        return 0;
      }
    }
    
    # Stringable List
    {
      my $list = List->new([]);

      my $minimal = TestCase::Minimal->new;
      $minimal->set_x(3);
      $minimal->set_y(2);

      my $simple = TestCase::Simple->new;
      $simple->set_x(1);
      $simple->set_y(2);

      $list->push($minimal);
      $list->push($simple);
      
      my $joined_string = "";
      for (my $i = 0; $i < $list->length; $i++) {
        my $stringable = (Stringable)$list->get($i);
        $joined_string .= $stringable->to_string;
      }
      
      unless ($joined_string eq "(3,2)(1,2):Simple") {
        return 0;
      }
    }
    
    # Cloneable minimal
    {
      my $minimal = TestCase::Minimal->new;
      $minimal->set_x(1);
      $minimal->set_y(2);
      
      my $cloneable = (Cloneable)$minimal;
      
      my $minimal2 = (TestCase::Minimal)$cloneable->clone;
      
      if ($minimal == $minimal2) {
        return 0;
      }
      
      unless ($minimal->to_string eq $minimal2->to_string) {
        return 0;
      }
    }

    # TestCase::Interface::Type
    {
      my $testcase_interface = TestCase::Interface->new;

      my $interface = (TestCase::Interface::Type)$testcase_interface;
      
      unless ($interface->interface_shared1(3) eq "shared1 3") {
        return 0;
      }
    }

    # TestCase::Interface::Type method not found
    {
      my $testcase_interface = TestCase::Interface->new;
      
      my $interface = (TestCase::Interface::Type)$testcase_interface;
      
      eval { $interface->interface_shared2(3); };
      unless ($@) {
        return 0;
      }
    }

    # isa
    {
      my $minimal = TestCase::Minimal->new;
      my $minimals = new TestCase::Minimal[3];
      
      unless ($minimal isa Stringable) {
        return 0;
      }

      if ($minimals isa Stringable) {
        return 0;
      }
    }
    
    {
      my $pointable : TestCase::Interface::Point = Point->new(1, 2);
      unless ($pointable->x == 1) {
        return 0;
      }
      unless ($pointable->y == 2) {
        return 0;
      }
      unless ($pointable->to_string eq "(1,2)") {
        return 0;
      }
    }
    
    # to_string
    {
      my $interface = new TestCase::Interface;
      unless ($interface->test_to_string) {
        return 0;
      }
    }
    
    # call_to_string
    {
      my $interface = new TestCase::Interface;
      unless ($interface->test_call_to_string) {
        return 0;
      }
    }

    # call_to_string_precompile
    {
      my $interface = new TestCase::Interface;
      unless ($interface->test_call_to_string_precompile) {
        return 0;
      }
    }
    $@ = undef;
    
    return 1;
  }
  
  method interface_shared1 : string ($num : int) {
    return "shared1 $num";
  }
  
  method test_to_string : int () {
    # Call to_string
    {
      my $string = $self->to_string;
      unless ($string eq "TestCase::Interface") {
        return 0;
      }
    }
    
    return 1;
  }

  method test_call_to_string : int () {
    # Call call_to_string
    {
      my $string = $self->call_to_string;
      unless ($string eq "call_to_string TestCase::Interface") {
        return 0;
      }
    }
    
    return 1;
  }

  method test_call_to_string_precompile : int () {
    # Call call_to_string_precompile
    {
      my $string = $self->call_to_string_precompile;
      unless ($string eq "call_to_string_precompile TestCase::Interface") {
        return 0;
      }
    }
    
    return 1;
  }
  
  
  # method interface_shared2 : string ($num : int);

  static method join_stringables : string ($stringables : Stringable[]) {
    my $string = "";
    
    for (my $i = 0; $i < @$stringables; $i++) {
      my $stringable = $stringables->[$i];
      $string .= $stringable->to_string;
    }
    
    return $string;
  }
  
  static method interface_array : int () {
    
    # Interface array
    {
      my $minimal = TestCase::Minimal->new_xy(1, 2);
      my $simple = TestCase::Simple->new_xy(3, 4);
      my $list = List->new([]);
      
      my $stringables = new Stringable[2];
      $stringables->[0] = $minimal;
      $stringables->[1] = $simple;
      
      my $string = &join_stringables($stringables);
      
      unless ($string eq "(1,2)(3,4):Simple") {
        return 0;
      }
    }
    # Interface array cast
    {
      my $minimals = [TestCase::Minimal->new_xy(1, 2), TestCase::Minimal->new_xy(3, 4)];
      
      my $stringables = (Stringable[])$minimals;

      my $string = &join_stringables($stringables);
      
      unless ($string eq "(1,2)(3,4)") {
        return 0;
      }

      my $minimal0 = TestCase::Minimal->new;
      $stringables->[0] = $minimal0;
      
      eval {
        my $simple1 = TestCase::Simple->new;
        $stringables->[1] = $simple1;
      };
      
      unless ($@) {
        return 0;
      }
    }
    
    $@ = undef;
    
    return 1;
  }

  static method interface_muldim_array : int () {
    
    # Interface array
    {
      my $stringable_muldim : Stringable[][] = new TestCase::Minimal[][3];
      $stringable_muldim->[0] = new TestCase::Minimal[3];
      $stringable_muldim->[0][0] = TestCase::Minimal->new_xy(1, 2);
      
      unless ($stringable_muldim->[0][0]->to_string eq "(1,2)") {
        return 0;
      }
      
      eval {
        $stringable_muldim->[0] = new Stringable[3];
      };
      unless ($@) {
        return 0;
      }
    }

    # Interface array
    {
      my $stringable_muldim = new Stringable[][3];
      $stringable_muldim->[0] = new Stringable[3];
      $stringable_muldim->[0][0] = TestCase::Minimal->new_xy(1, 2);
      
      unless ($stringable_muldim->[0][0]->to_string eq "(1,2)") {
        return 0;
      }
    }
    
    $@ = undef;
    
    return 1;
  }

  static method can : int () {
    # can
    {
      my $pointable : TestCase::Interface::Point = TestCase::PointForTest->new_xy(1, 2);
      
      unless ($pointable can x) {
        return 0;
      }
      unless ($pointable can y) {
        return 0;
      }
      unless ($pointable can to_string) {
        return 0;
      }
      
      if ($pointable can not_impl) {
        return 0;
      }

      unless ($pointable can "") {
        return 0;
      }
    }
    
    return 1;
  }

  method to_string : string () {
    return "TestCase::Interface";
  }
  
  method call_to_string : string () {
    return $self->TestCase::Interface::Type::call_to_string;
  }

  method call_to_string_precompile : string () {
    return $self->TestCase::Interface::Type::call_to_string_precompile;
  }
  
  static method void_and_any_object_return_type : int () {
    
    {
      my $ret = &any_object_interface(method : void () {
        
      });
      
      unless ($ret == undef) {
        return 0;
      }
    }
    
    return 1;
  }
  
  static method any_object_interface : object ($cb : TestCase::Interface::AnyObjectReturn) {
    
    return $cb->();
  }
  
}
