from test/more import *;
requires_capability( "proc" );
requires_capability( "fs" );
requires_capability( "clib" );

from std/clib import CLib;
from std/io import Path;
from std/proc import Env, Proc;

let fixture_root := Env.get("FIXTURE_DIR");
if ( fixture_root ≡ null or fixture_root eq "" ) {
	skip_all( "FIXTURE_DIR is not set" );
}
let clib_root := fixture_root _ "/example_clib";
let lib_path := clib_root _ "/libgreet.so";
let lib_file := new Path(lib_path);

if ( lib_file.exists() ) {
	pass("example C library exists");
}
else {
	let build := Proc.run(
		"gcc",
		[
			"-O2",
			"-Wall",
			"-Wextra",
			"-Wpedantic",
			"-fPIC",
			"-I" _ clib_root _ "/include",
			"-shared",
			"-o",
			lib_path,
			clib_root _ "/src/greet.c"
		],
		{
			capture_stdout: true,
			capture_stderr: true
		},
	);

	ok( Proc.is_success(build), "built example C library with gcc" );
	if ( not Proc.is_success(build) ) {
		diag( build{stdout} );
		diag( build{stderr} );
		diag( Proc.status_text(build) );
		bail_out("could not build example C library");
	}
}

let lib := CLib.open(lib_path);

let greet := lib.func(
	"greet",
	[],
	{
		type: "binary",
		terminated_by: "nul",
		free: "greet_free"
	},
);

let greet_person := lib.func(
	"greet_person",
	[ { type: "binary", terminated_by: "nul", nullable: true } ],
	{
		type: "binary",
		terminated_by: "nul",
		free: "greet_free"
	},
);

let add_i64 := lib.func(
	"greet_add_i64",
	[ { type: "int", bits: 64 }, { type: "int", bits: 64 } ],
	{ type: "int", bits: 64 },
);

let add_f64 := lib.func(
	"greet_add_f64",
	[ { type: "float", bits: 64 }, { type: "float", bits: 64 } ],
	{ type: "float", bits: 64 },
);

let bool_not := lib.func(
	"greet_not",
	[ "bool" ],
	"bool",
);

let noop := lib.func(
	"greet_noop",
	[],
	"null",
);

let free_count := lib.func(
	"greet_free_count",
	[],
	{ type: "int", bits: 64 },
);

let reset_free_count := lib.func(
	"greet_reset_free_count",
	[],
	"null",
);

let return_null := lib.func(
	"greet_return_null",
	[],
	{ type: "binary", terminated_by: "nul" },
);

let copy_bytes := lib.func(
	"greet_copy_bytes",
	[ "binary", { type: "int", bits: 64 } ],
	{ type: "binary", length_arg: 1, free: "greet_free" },
);

let count_bytes := lib.func(
	"greet_count_bytes",
	[ { type: "binary", nullable: true }, { type: "int", bits: 64 } ],
	{ type: "int", bits: 64 },
);

reset_free_count.call();

is( to_string(greet.call()), "Hello, world!", "greet returns bytes", );
is(
	to_string( greet_person.call(to_binary("Alice")) ),
	"Hello, Alice!",
	"greet_person accepts BinaryString",
);
is(
	to_string( greet_person.call(null) ),
	"Hello, world!",
	"greet_person accepts nullable binary parameter",
);
is( add_i64.call(40, 2), 42, "int64 parameters and return", );
is( add_f64.call(1.25, 2.5), 3.75, "float64 parameters and return", );
is( bool_not.call(true), false, "bool parameter and return", );
is( bool_not.call(false), true, "bool false parameter and return", );
is( noop.call(), null, "void return maps to null", );
is( return_null.call(), null, "null pointer binary return maps to null", );

let payload := to_binary("ABC");
is(
	to_string( copy_bytes.call(payload, 3) ),
	"ABC",
	"binary bytes round trip",
);
is(
	count_bytes.call(payload, 3),
	3,
	"binary parameter with explicit length",
);
is( count_bytes.call(null, 0), 0, "nullable binary parameter accepts null", );
is( free_count.call(), 4, "configured free function runs for owned returns", );
ok( lib.has_symbol("greet"), "has_symbol detects exported symbol", );
is( lib.close(), null, "close returns null", );
is( lib.close(), null, "double close returns null", );

done_testing();
