use v5.40;
use blib;
use Test2::Tools::Affix qw[:all];
use Test2::V0 -no_srand => 1;
use Affix qw[:all];
#
$|++;
#
# This C code will be compiled into a temporary library for many of the tests.
my $C_CODE = <<'END_C';
#include "std.h"
//ext: .c

typedef struct {
    int x;
    int y;
} Point;

typedef struct {
    Point top_left;
    Point bottom_right;
    char label[16];
} Rect;

// A callback with many arguments to test register/stack passing
typedef void (*kitchen_sink_cb)(
    int a, double b, int c, double d, int e, double f, int g, double h,
    const char* i, int* j
);
DLLEXPORT int call_kitchen_sink(kitchen_sink_cb cb) {
    int j_val = 100;
    cb(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, "kitchen sink", &j_val);
    return j_val + 1;
}

typedef int (*IntMap)(int);
DLLEXPORT int map_int(int val, IntMap cb) {
    return cb(val);
}

typedef void (*RectInspector)(Rect*);
DLLEXPORT void inspect_rect(Rect* r, RectInspector cb) {
    cb(r);
}

typedef Point (*PointGenerator)(void);
DLLEXPORT int check_point_gen(PointGenerator cb) {
    Point p = cb();
    return p.x + p.y;
}

typedef union {
    int i;
    float f;
    char c[8];
} MyUnion;

DLLEXPORT int invoke_union_cb(void (*cb)(MyUnion*)) {
    MyUnion u;
    u.i = 42;
    cb(&u);
    return u.i;
}
END_C
#
my $lib_path = compile_ok($C_CODE);
ok( $lib_path && -e $lib_path, 'Compiled a test shared library successfully' );
note 'Testing a callback with 10 mixed arguments passed as a direct coderef.';
subtest 'kitchen sink' => sub {
    isa_ok my $harness
        = wrap( $lib_path, 'call_kitchen_sink',
        [ Callback [ [ SInt32, Float64, SInt32, Float64, SInt32, Float64, SInt32, Float64, Pointer [Char], Pointer [SInt32] ] => Void ] ] => SInt32 ),
        ['Affix'];
    my $callback_sub = sub( $a, $b, $c, $d, $e, $f, $g, $h, $i, $j_ref ) {
        is $a,      1,              'Callback arg 1 (int)';
        is $b,      2.0,            'Callback arg 2 (double)';
        is $c,      3,              'Callback arg 3 (int)';
        is $d,      4.0,            'Callback arg 4 (double)';
        is $e,      5,              'Callback arg 5 (int)';
        is $f,      6.0,            'Callback arg 6 (double)';
        is $g,      7,              'Callback arg 7 (int)';
        is $h,      8.0,            'Callback arg 8 (double)';
        is $i,      'kitchen sink', 'Callback arg 9 (string)';
        is $$j_ref, 100,            'Callback arg 10 (int*)';
        $$j_ref = 200;
    };
    is $harness->($callback_sub), 201, 'return value';
};
subtest simple => sub {
    typedef Point => Struct [ x => Int, y => Int ];
    typedef Rect => Struct [ top_left => Point(), bottom_right => Point(), label => Array [ Char, 16 ] ];
    isa_ok my $map = wrap( $lib_path, 'map_int', [ Int, Callback [ [Int] => Int ] ] => Int ), ['Affix'];
    my $res = $map->(
        10,
        sub {
            my $v = shift;
            return $v * 2;
        }
    );
    is $res, 20, 'Simple callback executed';
    #
    isa_ok my $inspect = wrap( $lib_path, 'inspect_rect', [ Pointer [ Rect() ], Callback [ [ Pointer [ Rect() ] ] => Void ] ] => Void ), ['Affix'];
    my $r = { top_left => { x => 1, y => 1 }, bottom_right => { x => 2, y => 2 }, label => "Check" };
    my $seen_label;
    $inspect->(
        $r,
        sub {
            my $struct = shift;                    # Arrives as the magical HashRef directly
            $seen_label = $struct->{label};
            $struct->{label} = "Checked";          # Magical write-back
        }
    );
    is $seen_label, "Check", 'Callback received struct pointer correctly';
    #
    isa_ok my $chk_pt = wrap( $lib_path, 'check_point_gen', [ Callback [ [] => Point() ] ] => Int ), ['Affix'];
    my $sum = $chk_pt->(
        sub {
            return { x => 7, y => 8 };
        }
    );
    is $sum, 15, 'Callback returned struct by value correctly';
};
subtest 'unions passed to callbacks' => sub {
    ok typedef( MyUnion => Union [ i => SInt32, f => Float32, c => Array [ Char, 8 ] ] ), 'typedef @MyUnion';
    isa_ok my $invoke = wrap( $lib_path, 'invoke_union_cb', [ Callback [ [ Pointer [ MyUnion() ] ] => Void ] ] => Int ), ['Affix'];
    my $cb = sub($u) {
        is $u->{i}, 42, 'Read integer member from union pointer directly';    # magical

        # IEEE 754 2.0f is 0x40000000 (1073741824 decimal)
        $u->{f} = 2.0;
    };
    my $ret = $invoke->($cb);

    # 0x40000000 is 1073741824. Overwriting the union proves the callback write persisted.
    is $ret, 1073741824, 'Callback modifications persisted to C (Union write-back)';
};
subtest 'ThisCall Sugar' => sub {
    my $cb = ThisCall( Callback( [ [ Int() ] => Void() ] ) );
    is $cb->signature, '*((*void,int)->void)', 'ThisCall prepends Pointer[Void] (*void)';
    my $str = ThisCall('*((int)->void)');
    is $str, '*((*void,int)->void)', 'ThisCall also works on signature strings';
};
#
done_testing;
