use strict;
use warnings;
use Test::More;
use FFI::Platypus;
use FFI::CheckLib;

my $ffi = FFI::Platypus->new;
$ffi->lib(find_lib lib => 'test', symbol => 'f0', libpath => 't/ffi');
$ffi->type('string(10)' => 'string_10');
$ffi->type('string(5)'  => 'string_5');

$ffi->attach( 'string_matches_foobarbaz'     => ['string'] => 'int');
$ffi->attach( 'string_return_foobarbaz'      => []       => 'string');
$ffi->attach( [pointer_null => 'null']       => []       => 'string');
$ffi->attach( [pointer_is_null => 'is_null'] => ['string'] => 'int');
$ffi->attach( 'string_write_to_string'       => ['string','string'] => 'void');

ok string_matches_foobarbaz("foobarbaz"), "string_matches_foobarbaz(foobarbaz) = true";
ok !string_matches_foobarbaz("x"), "string_matches_foobarbaz(foobarbaz) = false";
is string_return_foobarbaz(), "foobarbaz", "string_return_foobarbaz() = foobarbaz";

is null(), undef, 'null() = undef';
is is_null(undef), 1, 'is_null(undef) = 1';
is is_null(), 1, 'is_null() = 1';
is is_null("foo"), 0, 'is_null("foo") = 0';

$ffi->attach( [string_set_closure => 'set_closure']   => ['(string)->void'] => 'void');
$ffi->attach( [string_call_closure => 'call_closure'] => ['string']=>'void');

my $save = 1;
my $closure = $ffi->closure(sub { $save = $_[0] });

set_closure($closure);
call_closure("hey there");
is $save, "hey there", "\$save = hey there";

call_closure(undef);
is $save, undef, "\$save = undef";


$ffi->attach( ['string_matches_foobarbaz' => 'fixed_input_test'] => ['string_10'] => 'int');
$ffi->attach( ['pointer_is_null'          => 'fixed_input_is_null'] => ['string_10'] => 'int');

is fixed_input_test("foobarbaz\0"), 1, "fixed_input_test(foobarbaz\\0)";
is fixed_input_is_null(undef), 1, "fixed_input_is_null(undef)";

$ffi->attach( string_fixed_test => ['int'] => 'string_5');

is string_fixed_test(0), "zero ", "string_fixed_text(0) = zero";
is string_fixed_test(1), "one  ", "string_fixed_text(1) = one";
is string_fixed_test(2), "two  ", "string_fixed_text(2) = two";
is string_fixed_test(3), "three", "string_fixed_text(3) = three";

$ffi->attach( [pointer_null => 'fixed_output_null'] => [] => 'string_5');

is fixed_output_null(), undef, 'fixed_output_null()';

$ffi->attach( [string_set_closure => 'set_closure_fixed'] => ['(string_5)->void'] => 'void');

my $closure_fixed = $ffi->closure(sub { $save = $_[0] });

set_closure_fixed($closure_fixed);
call_closure("zero one  two  three");
is $save, "zero ", "save=zero ";

$ffi->attach( string_test_pointer_arg => [ 'string*' ] => 'string' );

{
  my $arg = "foo";
  is( string_test_pointer_arg(\$arg), "*arg==foo");
  is( $arg, "out" );
}

{
  my $arg;
  is( string_test_pointer_arg(\$arg), "*arg==NULL");
  is( $arg, "out" );
}

is( string_test_pointer_arg(undef), "arg==NULL");

$ffi->attach( string_test_pointer_ret => [ 'string' ] => 'string*' );
$ffi->attach( [ pointer_null => 'string_test_pointer_ret_null' ] => [] => 'string*' );

is_deeply( string_test_pointer_ret("foo"), \"foo" );
is_deeply( string_test_pointer_ret(undef), \undef );
is_deeply( [string_test_pointer_ret_null()], [] );

subtest 'fixed length input' => sub {

  $ffi->type('string[5]' => 'string_5_undef');

  my $a2 = $ffi->function(get_string_from_array => ['string_5_undef', 'int'] => 'string');

  my @list = ( 'foo', 'bar', 'baz', undef, 'five', 'six' );

  subtest 'with default' => sub {
    is $a2->(\@list, 0), 'foo', 'a2(0) = foo';
    is $a2->(\@list, 1), 'bar', 'a2(0) = bar';
    is $a2->(\@list, 2), 'baz', 'a2(0) = baz';
    is $a2->(\@list, 3), undef, 'a2(0) = undef';
    is $a2->(\@list, 4), 'five', 'a2(0) = five';
  };

};

subtest 'variable length input' => sub {

  $ffi->type('string[]' => 'sa');

  my $get_string_from_array = $ffi->function(get_string_from_array => ['sa','int'] => 'string');

  my @list = ('foo', 'bar', 'baz', undef );

  for(0..2)
  {
    is $get_string_from_array->(\@list, $_), $list[$_], "get_string_from_array(\@list, $_) = $list[$_]";
  }

  is $get_string_from_array->(\@list, 3), undef, "get_string_from_array(\@list, 3) = undef";
};

subtest 'fixed length return' => sub {

  $ffi->type('string[3]' => 'sa3');

  is(
    $ffi->function(pointer_null => [] => 'sa3')->call,
    undef,
    'returns null',
  );

  is_deeply(
    $ffi->function(onetwothree3 => [] => 'sa3')->call,
    [ qw( one two three ) ],
    'returns with just strings',
  );

  is_deeply(
    $ffi->function(onenullthree3 => [] => 'sa3')->call,
    [ 'one', undef, 'three' ],
    'returns with NULL/undef in the middle',
  );

};

subtest 'null terminated return' => sub {

  is(
    $ffi->function(pointer_null => [] => 'sa')->call,
    undef,
    'returns null',
  );

  is_deeply(
    $ffi->function('onetwothree4', => [] => 'sa')->call,
    [ qw( one two three ) ],
  );

  is_deeply(
    $ffi->function('onenullthree3' => [] => 'sa')->call,
    [ qw( one ) ],
  );

  is_deeply(
    $ffi->function('ptrnull' => [] => 'sa')->call,
    [],
  );

};

subtest 'argument update' => sub {

  my @args = ( undef, 'six', 'xx' );
  $ffi->function( string_array_arg_update => [ 'string[3]' ] => 'void' )->call(\@args);
  is_deeply(
    \@args,
    [ "one", "two", "xx" ],
  );

};

subtest 'write to string' => sub {

  my $src = 'hello world';
  my $dst = ' ' x (length($src)+1);
  string_write_to_string($dst, $src);
  is($dst, "hello world\0");

};

done_testing;
