package TestCase::Sub {
  use TestCase::Minimal;
  use TestCase::Point_3b;
  use TestCase::Point_3s;
  use TestCase::Point_3i;
  use TestCase::Point_3l;
  use TestCase::Point_3f;
  use TestCase::Point_3d;
  use SPVM::Comparator;
  use TestCase::Simple (import_sub1, import_sub2);
  use SPVM::Util (INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX, INT32_MIN, INT32_MAX, INT64_MIN, INT64_MAX, FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX);
  
  sub test_import_sub : int () {
    unless (import_sub1() == 1) {
      return 0;
    }
    
    unless (import_sub2() == 2) {
      return 0;
    }
    
    return 1;
  }
  
  sub already_exists_sub_no_arg : int () {
    return 3;
  }

  sub already_exists_sub : int ($num1 : int, $num2 : int) {
    return $num1 + $num2;
  }

  sub test_return_value_automatical_numeric_convertion_widening : double () {
    
    return 2;
  }

  sub test_return_value_automatical_numeric_convertion_narrowing_byte : byte () {
    
    return 127;
  }

  sub test_return_value_automatical_numeric_convertion_narrowing_short : short () {
    
    return 32767;
  }
  
  sub return_value_automatical_numeric_convertion : int () {
    {
      my $num : double = test_return_value_automatical_numeric_convertion_widening();
      unless ($num == 2.0) {
        return 0;
      }
    }
    
    {
      my $num : byte = test_return_value_automatical_numeric_convertion_narrowing_byte();
      unless ($num == 127) {
        return 0;
      }
    }

    {
      my $num : short = test_return_value_automatical_numeric_convertion_narrowing_short();
      unless ($num == 32767) {
        return 0;
      }
    }
    
    return 1;
  }
  
  sub test_vaarg_objects_pass_array : int ($string : string, $nums : object[]...) {
    
    unless ($string eq "a") {
      return 0;
    }
    
    unless (@$nums == 3) {
      return 0;
    }
    
    unless ((int)$nums->[0] == 1 && (int)$nums->[1] == 2 && (int)$nums->[2] == 3) {
      return 0;
    }
    
    return 1;
  }
  
  sub vaarg_objects_pass_array : int () {
    return test_vaarg_objects_pass_array("a", [(object)1, 2, 3]);
  }

  sub test_vaarg_objects_pass_each_values : int ($string : string, $nums : object[]...) {
    
    unless ($string eq "a") {
      return 0;
    }
    
    unless (@$nums == 3) {
      return 0;
    }
    
    unless ((int)$nums->[0] == 1 && (int)$nums->[1] == 2 && (int)$nums->[2] == 3) {
      return 0;
    }
    
    return 1;
  }
  
  sub vaarg_objects_pass_each_values : int () {
    return test_vaarg_objects_pass_each_values("a", 1, 2, 3);
  }

  sub test_vaarg_pass_array : int ($string : string, $nums : int[]...) {
    
    unless ($string eq "a") {
      return 0;
    }
    
    unless (@$nums == 3) {
      return 0;
    }
    
    unless ($nums->[0] == 1 && $nums->[1] == 2 && $nums->[2] == 3) {
      return 0;
    }
    
    return 1;
  }
  
  sub vaarg_pass_array : int () {
    return test_vaarg_pass_array("a", [1, 2, 3]);
  }

  sub test_vaarg_pass_each_values : int ($string : string, $nums : int[]...) {
    
    unless ($string eq "a") {
      return 0;
    }
    
    unless (@$nums == 3) {
      return 0;
    }
    
    unless ($nums->[0] == 1 && $nums->[1] == 2 && $nums->[2] == 3) {
      return 0;
    }
    
    return 1;
  }
  
  sub vaarg_pass_each_values : int () {
    return test_vaarg_pass_each_values("a", 1, 2, 3);
  }
  
  sub cb_obj_capture : int () {
    my $capture1 = 7;
    my $capture2 = 10;
    my $cb_obj = [$capture1 : int, $capture2 : int] sub : int ($self : self, $x1 : object, $x2 : object) {
      
      unless ($capture1 == 7) {
        return 0;
      }
      
      unless ($capture2 == 10) {
        return 0;
      }
      
      unless ($self->{capture1} == 7) {
        return 0;
      }

      $self->{capture1} = 5;
      
      unless ($capture1 == 5) {
        return 0;
      }
      
      return 1;
    };
    
    return $cb_obj->(undef, undef);
  }
  
  sub cb_obj_call_cb_obj : int () {
    my $cb_obj = sub : int ($self : self, $x1 : int, $x2 : int) {
      return $x1 + $x2;
    };
    
    my $total = $cb_obj->(1, 3);
    
    unless ($total == 4) {
      return 0;
    }
    
    return 1;
  }

  sub cb_obj_call_cb_obj_from_callback : int () {
    my $cb_obj = sub : int ($self : self, $x1 : object, $x2 : object) {
      my $x1_num = (int)$x1;
      my $x2_num = (int)$x2;
      return $x1_num + $x2_num;
    };
    
    my $comparator : SPVM::Comparator = $cb_obj;
    
    my $total = $comparator->(2, 3);
    
    unless ($total == 5) {
      return 0;
    }
    
    return 1;
  }
  
  sub return_value_byte_sub : TestCase::Point_3b () {
    my $value : TestCase::Point_3b;
    $value->{x} = INT8_MIN();
    $value->{y} = 1;
    $value->{z} = 2;
    
    return $value;
  }
  
  sub return_value_byte : int () {
    my $value : TestCase::Point_3b = return_value_byte_sub();
    
    if ($value->{x} == INT8_MIN()) {
      if ($value->{y} == 1) {
        if ($value->{z} == 2) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub return_value_short_sub : TestCase::Point_3s () {
    my $value : TestCase::Point_3s;
    $value->{x} = INT16_MIN();
    $value->{y} = 1;
    $value->{z} = 2;
    
    return $value;
  }
  
  sub return_value_short : int () {
    my $value : TestCase::Point_3s = return_value_short_sub();
    
    if ($value->{x} == INT16_MIN()) {
      if ($value->{y} == 1) {
        if ($value->{z} == 2) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub return_value_int_sub : TestCase::Point_3i () {
    my $value : TestCase::Point_3i;
    $value->{x} = INT32_MIN();
    $value->{y} = 1;
    $value->{z} = 2;
    
    return $value;
  }
  
  sub return_value_int : int () {
    my $value : TestCase::Point_3i = return_value_int_sub();
    
    if ($value->{x} == INT32_MIN()) {
      if ($value->{y} == 1) {
        if ($value->{z} == 2) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub return_value_long_sub : TestCase::Point_3l () {
    my $value : TestCase::Point_3l;
    $value->{x} = INT64_MIN();
    $value->{y} = 1;
    $value->{z} = 2;
    
    return $value;
  }
  
  sub return_value_long : int () {
    my $value : TestCase::Point_3l = return_value_long_sub();
    
    if ($value->{x} == INT64_MIN()) {
      if ($value->{y} == 1) {
        if ($value->{z} == 2) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub return_value_float_sub : TestCase::Point_3f () {
    my $value : TestCase::Point_3f;
    $value->{x} = FLT_MIN();
    $value->{y} = 0.25f;
    $value->{z} = 0.5f;
    
    return $value;
  }
  
  sub return_value_float : int () {
    my $value : TestCase::Point_3f = return_value_float_sub();
    
    if ($value->{x} == FLT_MIN()) {
      if ($value->{y} == 0.25f) {
        if ($value->{z} == 0.5f) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub return_value_double_sub : TestCase::Point_3d () {
    my $value : TestCase::Point_3d;
    $value->{x} = DBL_MIN();
    $value->{y} = 0.25;
    $value->{z} = 0.5;
    
    return $value;
  }
  
  sub return_value_double : int () {
    my $value : TestCase::Point_3d = return_value_double_sub();
    
    if ($value->{x} == DBL_MIN()) {
      if ($value->{y} == 0.25) {
        if ($value->{z} == 0.5) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub sub_push_arg_undef : TestCase::Minimal ($minimal : TestCase::Minimal) {
    return $minimal;
  }

  sub push_arg_undef : int () {
  
    my $minimal = sub_push_arg_undef(undef);
    
    if ($minimal == undef) {
      return 1;
    }
    
    return 0;
  }

  # Default return value empty
  sub default_return_value_byte : int () {
    
    if (default_return_value_byte_sub() == 0) {
      if (default_return_value_byte_sub_empty() == 0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_byte_sub : byte () {
    1;
  }
  sub default_return_value_byte_sub_empty : byte () {
    
  }
  sub default_return_value_short : int () {
    
    if ((int)default_return_value_short_sub() == (int)(short)0) {
      if ((int)default_return_value_short_sub_empty() == (int)(short)0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_short_sub : short () {
    1;
  }
  sub default_return_value_short_sub_empty : short () {
    
  }
  sub default_return_value_int : int () {
    
    if (default_return_value_int_sub() == (int)0) {
      if (default_return_value_int_sub_empty() == (int)0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_int_sub : int () {
    1;
  }
  sub default_return_value_int_sub_empty : int () {
    
  }
  sub default_return_value_long : int () {
    
    if (default_return_value_long_sub() == (long)0) {
      if (default_return_value_long_sub_empty() == (long)0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_long_sub : long () {
    1;
  }
  sub default_return_value_long_sub_empty : long () {
    
  }
  sub default_return_value_float : int () {
    
    if (default_return_value_float_sub() == (float)0) {
      if (default_return_value_float_sub_empty() == (float)0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_float_sub : float () {
    1;
  }
  sub default_return_value_float_sub_empty : float () {
    
  }
  sub default_return_value_double : int () {
    
    if (default_return_value_double_sub() == (double)0) {
      if (default_return_value_double_sub_empty() == (double)0) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_double_sub : double () {
    1;
  }
  sub default_return_value_double_sub_empty : double () {
    
  }
  sub default_return_value_object : int () {
    
    if (default_return_value_object_sub() == undef) {
      if (default_return_value_object_sub_empty() == undef) {
        return 1;
      }
    }
    
    return 0;
  }
  sub default_return_value_object_sub : TestCase::Minimal () {
    1;
  }
  sub default_return_value_object_sub_empty : TestCase::Minimal () {
    
  }
  
  # Call void function
  sub call_void_sub : void ($nums : int[]) {
    $nums->[0] = 5;
  }
  sub call_void : int () {
    my $nums = [1];
    
    call_void_sub($nums);
    
    if ($nums->[0] == 5) {
      return 1;
    }
    return 0;
  }
  
  sub call_sub_nest : int () {
    my $total = call_sub_nest_sum(call_sub_nest_sum(1, 2), call_sub_nest_sum(3, 4));
    
    if ($total == 10) {
      return 1;
    }
    
    return 0;
  }
  sub call_sub_nest_sum : int ($num1 : int, $num2 : int) {
    return $num1 + $num2;
  }
  sub call_sub_args_convertion_stab1 : double ($var1 : byte, $var2 : short, $var3 : int, $var4 : long, $var5 : float, $var6 : double) {
    return ($var1 + $var2 + $var3 + $var4 + $var5 + $var6);
  }
  
  sub call_sub_args_convertion : int () {
    # Constant assignment
    my $success_constant_narrow = 0;
    {
      my $return_value1 = call_sub_args_convertion_stab1(1, 2, 3, 2, 8, 16);
      if ($return_value1 == 32) {
        $success_constant_narrow = 1;
      }
    }
    
    # Widning
    my $success_constant_wide = 0;
    {
      my $return_value1 = call_sub_args_convertion_stab1((byte)1, (byte)2, (byte)3, (byte)2, (byte)8, (byte)16);
      
      if ($return_value1 == 32) {
        $success_constant_wide = 1;
      }
    }
    
    if ($success_constant_narrow && $success_constant_wide) {
      return 1;
    }
    
    return 0;
  }

  # call_sub arguments
  sub call_sub_args_byte : int ($var1 : byte, $var2 : byte, $var3 : byte) {
    if ($var1 == 0) {
      if ($var2 == 127) {
        if ($var3 == -128) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  sub call_sub_args_short : int ($var1 : short, $var2 : short, $var3 : short) {
    if ((int)$var1 == (int)(short)0) {
      if ((int)$var2 == (int)(short)32767) {
        if ((int)$var3 == (int)(short)-32768) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  sub call_sub_args_int : int ($var1 : int, $var2 : int, $var3 : int) {
    if ($var1 == 0) {
      if ($var2 == 2147483647) {
        if ($var3 == -2147483648) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  
  sub call_sub_args_long : int ($var1 : long, $var2 : long, $var3 : long) {
    if ($var1 == 0L) {
      if ($var2 == 9223372036854775807L) {
        if ($var3 == -9223372036854775808L) {
          return 1;
        }
      }
    }
    
    return 0;
  }
  # byte array argument
  sub call_sub_barray : byte ($nums : byte[]) {
    
    my $total = (byte)0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = (byte)((int)$total + (int)$nums->[$i]);
    }
    
    return $total;
  }
  
  # short array argument
  sub call_sub_sarray : short ($nums : short[]) {
    
    my $total = (short)0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = (short)((int)$total + (int)$nums->[$i]);
    }
    
    return $total;
  }
  
  # int array argument
  sub call_sub_iarray : int ($nums : int[]) {
    
    my $total = 0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = $total + $nums->[$i];
    }
    
    return $total;
  }

  # long array argument
  sub call_sub_larray : long ($nums : long[]) {
    
    my $total = (long)0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = $total + $nums->[$i];
    }
    
    return $total;
  }

  # float array argument
  sub call_sub_farray : float ($nums : float[]) {
    
    my $total = (float)0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = $total + $nums->[$i];
    }
    
    return $total;
  }

  # double array argument
  sub call_sub_darray : double ($nums : double[]) {
    
    my $total = (double)0;
    for (my $i = 0; $i < @$nums; $i++) {
      $total = $total + $nums->[$i];
    }
    
    return $total;
  }
  # call_sub return value
  sub call_sub_return_barray  : byte[] () {
    my $nums = new byte[3];
    
    $nums->[0] = (byte)1;
    $nums->[1] = (byte)2;
    $nums->[2] = (byte)3;
    
    return $nums;
  }
  sub call_sub_return_barray_check : int ($nums : byte[]) {
    
    if ($nums->[0] == 1) {
      if ($nums->[1] == 2) {
        if ($nums->[2] == 3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }

  sub call_sub_return_sarray : short[] () {
    my $nums = new short[3];
    
    $nums->[0] = (short)1;
    $nums->[1] = (short)2;
    $nums->[2] = (short)3;
    
    return $nums;
  }
  sub call_sub_return_sarray_check : int ($nums : short[]) {
    
    if ((int)$nums->[0] == (int)(short)1) {
      if ((int)$nums->[1] == (int)(short)2) {
        if ((int)$nums->[2] == (int)(short)3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }

  sub call_sub_return_iarray : int[] () {
    my $nums = new int[3];
    
    $nums->[0] = (int)1;
    $nums->[1] = (int)2;
    $nums->[2] = (int)3;
    
    return $nums;
  }
  sub call_sub_return_iarray_check : int ($nums : int[]) {
    
    if ($nums->[0] == (int)1) {
      if ($nums->[1] == (int)2) {
        if ($nums->[2] == (int)3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }

  sub call_sub_return_larray : long[] () {
    my $nums = new long[3];
    
    $nums->[0] = (long)1;
    $nums->[1] = (long)2;
    $nums->[2] = (long)3;
    
    return $nums;
  }
  sub call_sub_return_larray_check : int ($nums : long[]) {
    
    if ($nums->[0] == (long)1) {
      if ($nums->[1] == (long)2) {
        if ($nums->[2] == (long)3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }

  sub call_sub_return_farray : float[] () {
    my $nums = new float[3];
    
    $nums->[0] = (float)1;
    $nums->[1] = (float)2;
    $nums->[2] = (float)3;
    
    return $nums;
  }
  sub call_sub_return_farray_check : int ($nums : float[]) {
    
    if ($nums->[0] == (float)1) {
      if ($nums->[1] == (float)2) {
        if ($nums->[2] == (float)3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }

  sub call_sub_return_darray : double[] () {
    my $nums = new double[3];
    
    $nums->[0] = (double)1;
    $nums->[1] = (double)2;
    $nums->[2] = (double)3;
    
    return $nums;
  }
  sub call_sub_return_darray_check : int ($nums : double[]) {
    
    if ($nums->[0] == (double)1) {
      if ($nums->[1] == (double)2) {
        if ($nums->[2] == (double)3) {
          if (@$nums == 3) {
            return 1;
          }
        }
      }
    }
    
    return 0;
  }
  sub call_sub_assign : int () {
    my $m = TestCase::Minimal->new();
    $m = TestCase::Minimal->new();
  }
  
  sub call_sub_undef : int ($value : TestCase::Minimal) {
    
    if ($value == undef) {
      return 1;
    }
    
    return 0;
  }

  sub call_sub_last_camma : int () {
    
    my $total = sum_int(3, 4,);
    
    if ($total == 7) {
      return 1;
    }
    
    return 0;
  }
  sub sum_int : int ($a : int, $b :int) {
    
    my $total = $a + $b;
    
    return $total;
  }

}
