class TestCase::Interface {
  use TestCase::Minimal;
  use TestCase::Simple;
  use Stringable;
  use Cloneable;
  use List;
  use Point;
  use TestCase::Pointable;
  use TestCase::PointForTest;
  
  interface TestCase::InterfaceType;
  
  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->cloneable_clone;
      
      if ($minimal == $minimal2) {
        return 0;
      }
      
      unless ($minimal->to_string eq $minimal2->to_string) {
        return 0;
      }
    }

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

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

    # TestCase::InterfaceType method not found
    {
      my $testcase_interface = TestCase::Interface->new;
      
      my $interface = (TestCase::InterfaceType)$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;
      }
    }
    
    # Pointable
    {
      my $pointable : TestCase::Pointable = Point->new_xy(1, 2);
      unless ($pointable->x == 1) {
        return 0;
      }
      unless ($pointable->y == 2) {
        return 0;
      }
      unless ($pointable->to_string eq "(1,2)") {
        return 0;
      }
    }
    
    $@ = undef;
    
    return 1;
  }
  
  method interface_shared1 : string ($num : int) {
    return "shared1 $num";
  }

  # 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 has_impl : int () {
    # has_impl
    {
      my $pointable : TestCase::Pointable = TestCase::PointForTest->new_xy(1, 2);
      
      unless (has_impl $pointable->x) {
        return 0;
      }
      unless (has_impl $pointable->y) {
        return 0;
      }
      unless (has_impl $pointable->to_string) {
        return 0;
      }
      
      if (has_impl $pointable->not_impl) {
        return 0;
      }

      unless (has_impl $pointable) {
        return 0;
      }
    }
    
    return 1;
  }
}