#!perl -w

# $Id$

# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'

use strict;

#use Config; # ?not used
use File::Spec;
use Test::More;
use Encode;
plan tests => 48;
use vars qw($function $result $input $test_dll $ptr);

use_ok('Win32::API');
use_ok('Win32::API::Test');
use_ok('Win32');

ok(1, 'loaded');

# Reset errors before starting?
$^E = 0;

# On cygwin, $$ is different from Win32 process id
my $cygwin = $^O eq 'cygwin';

$test_dll = Win32::API::Test::find_test_dll();
diag('API test dll found at (' . $test_dll . ')');
ok(-e $test_dll, 'found API test dll');
#$Win32::API::DEBUG = 1;

 
SKIP: {

    # TODO Check if this test still makes sense in 2008
    if (not Win32::IsWinNT()) {
        skip('because GetCurrentProcessId() not available on non-WinNT platforms', 3);
    }

    # Simple test, from kernel32
    $function = new Win32::API("kernel32", "GetCurrentProcessId", "", "N");
    ok(defined($function), 'GetCurrentProcessId() function found');

    $result = $function->Call();

    if ($cygwin) {
        $result = Cygwin::winpid_to_pid($result);
        diag('Cygwin::winpid_to_pid()=', $result);
    }
    ok($result == $$, 'GetCurrentProcessId() result ok');

    # Same as above, with prototype
    $function = new Win32::API("kernel32", "DWORD GetCurrentProcessId(  )");

    #diag("$function->{procname} \$^E=", $^E);
    $result = $function->Call();

    if ($cygwin) {
        $result = Cygwin::winpid_to_pid($result);
        diag('Cygwin::winpid_to_pid()=', $result);
    }
    ok($result == $$, 'GetCurrentProcessId() result ok');

    # Same as above, with Import
    ok( Win32::API->Import("kernel32", "DWORD GetCurrentProcessId(  )"),
        'Import of GetCurrentProcessId() function from kernel32.dll'
    );
    $result = GetCurrentProcessId();

    if ($cygwin) {
        $result = Cygwin::winpid_to_pid($result);
        diag('Cygwin::winpid_to_pid()=', $result);
    }

    ok($result == $$, 'GetCurrentProcessId() result ok');
}

#check if DLL handle leaks after not finding a func

{
    my $IsBadReadPtr = Win32::API->new(
        'kernel32.dll', 'BOOL IsBadReadPtr( UINT_PTR lp, UINT_PTR ucb)',
    );
    ok($IsBadReadPtr, 'Import of IsBadReadPtr function from kernel32.dll');
    my $dllhandle = Win32::LoadLibrary($test_dll);
    my $nofunction = new Win32::API($test_dll, 'int ThisFunctionDoesntExist(int a, int b)');
    die "function that doesn't exist, exists!" if $nofunction;
    Win32::FreeLibrary($dllhandle);
    ok($IsBadReadPtr->Call($dllhandle, 4), 'API test dll was freed');
}


### tests from our own DLL
#on x64 the return value is + since the test value is way under 2^63
SKIP: {
if(length(pack('J', 0)) == 8){
    skip("ULONG is an 8 byte integer on x64 on old api", 2);
}

#API_TEST_API ULONG __stdcall highbit_unsigned() {
#0000000002B01170 40 57            push        rdi  
#	return 0x80005000;
#0000000002B01172 B8 00 50 00 80   mov         eax,80005000h 
#}
#0000000002B01177 5F               pop         rdi  
#0000000002B01178 C3               ret              
#--- No source file -------------------------------------------
#on X64 the mov did zero out the high 4 bytes on rax for me, i *think* no risk
#of a garbage high 4 bytes appearing in the SVUV even though ULONG is a 4
#byte int in theory
# test return value is signed  for unsigned proto on old API
$function = new Win32::API($test_dll, 'ULONG __stdcall highbit_unsigned()');
ok(defined($function), 'highbit_unsigned() function defined');
$result = $function->Call();
is($result, unpack('l', pack('L', 0x80005000)), 'return value for unsigned is signed on old API');
}

{
#old API psuedo pointer handling
my $pass = 1;
my $hnd = "\x00" x length(pack('J', 0));
$function = new Win32::API($test_dll, 'BOOL __stdcall GetHandle(LPHANDLE pHandle)');

$pass = $pass && defined($function);
#takes "\xAB\xCD\xED\x00"
$pass = $pass && $function->Call($hnd) == 1;
$hnd = unpack('J', $hnd);
$pass = $pass && $hnd == 4000;
ok($pass, 'GetHandle operates correctly');
$pass = 1;
$function = new Win32::API($test_dll, 'BOOL __stdcall FreeHandle(HANDLE Handle)');
$pass = $pass && defined($function);
#takes 123
$pass = $pass && $function->Call($hnd) == 1;
ok($pass, 'FreeHandle operates correctly');
}



# Sum 2 integers
$function = new Win32::API($test_dll, 'int sum_integers(int a, int b)');
ok(defined($function), 'sum_integers() function defined');

#diag("$function->{procname} \$^E=", $^E);
is($function->Call(2, 3), 5, 'function call with integer arguments and return value');

# Same as above, with a pointer
$function = new Win32::API($test_dll, 'int sum_integers_ref(int a, int b, int* c)');
ok(defined($function), 'sum_integers_ref() function defined');

#diag("$function->{procname} \$^E=", $^E);
$result = 0;
is($function->Call(2, 3, $result), 1, 'sum_integers_ref() returns the expected value');
is(unpack('C', $result), 5, 'sum_integers_ref() correctly modifies its ref argument');

#as of 0.71, creating an obj with callback or struct return type dies
eval{
$function = new Win32::API($test_dll, 'short  __stdcall sum_shorts_ref(short a, short b, short *c)');
};
ok(index($@, 'Win32::API invalid return type, structs and callbacks') != -1,
   'short as return type croak because they are structs on old API');
   
#changed to int, short really, b/c 0.71 type check
$function = new Win32::API($test_dll, 'int  __stdcall sum_shorts_ref(short a, short b, short *c)');
ok(defined($function), 'sum_shorts_ref() function defined');

#diag("$function->{procname} \$^E=", $^E);
$result = 0;
eval {$function->Call(2, 3, $result);};
is($@, "Win32::API::Call: parameter 1 must be a Win32::API::Struct object!\n"
   , 'shorts croak as struct objs on old API');

# Sum 2 doubles
$function = new Win32::API($test_dll, 'double sum_doubles(double a, double b)');
ok(defined($function), 'API_test.dll sum_doubles function defined');

#diag("$function->{procname} \$^E=",$^E);
ok($function->Call(2.5, 3.2) == 5.7, 'function call with double arguments');

# Same as above, with a pointer
$function =
    new Win32::API($test_dll, 'int sum_doubles_ref(double a, double b, double* c)');
ok(defined($function), 'sum_doubles_ref() function defined');

#diag("$function->{procname} \$^E=", $^E);
#in 0.68 this test caused a buffer overflow, changed to not cause one
$result = "\x00" x 8;
is($function->Call(2.5, 3.2, $result), 1, 'sum_doubles_ref() call works');

ok((unpack('d', $result) - 5.7 < 0.005), 'sum_doubles_ref() sets ref correctly');

# Sum 2 floats
$function = new Win32::API($test_dll, 'float sum_floats(float a, float b)');
ok(defined($function), 'sum_floats() function defined');

#diag("$function->{procname} \$^E=", $^E);
my $res = $function->Call(2.5, 3.2);
#due to rounding error, compare as strings in native format
is(pack('f', $res),  pack('f', 5.7), 'sum_floats() result correct');

# Same as above, with a pointer
$function = new Win32::API($test_dll, 'int sum_floats_ref(float a, float b, float* c)');
ok(defined($function), 'sum_floats_ref() function defined');

#diag("$function->{procname} \$^E=", $^E);
$result = "\x00" x 4;
is($function->Call(2.5, 3.2, $result),
    1, 'sum_floats_ref() returns the expected value (1)');

ok((unpack('f', $result) - 5.7 < 0.005), 'sum_floats_ref() call works');


# Find a char in a string
$function = new Win32::API($test_dll, 'char* find_char(char* string, char ch)');
ok(defined($function), 'find_char() function defined');

#diag("$function->{procname} \$^E=", $^E);
my $string = 'japh';
my $char   = 'a';
is($function->Call($string, $char), 'aph', 'find_char() function call works');

#testing chars on old API, chars as chars, char as return type was broken in  0.68 and older
$function = new Win32::API($test_dll, 'char __stdcall sum_char_ref(char a, char b, char* c)');

$result = "\x00";
is($function->Call("\x02", "\x03", $result), pack('c', -128), 'sum_char_ref() returns the expected value');
is(substr($result,0,1), "\x05", 'sum_char_ref() correctly modifies its ref argument');


#testing unsigned prefix chars (numeric handling), new in 0.69, unparsable in 0.68
#0xFF00 tests casting truncation behaviour
$function = new Win32::API($test_dll, 'char __stdcall sum_char_ref(signed char a, signed char b, char* c)');

$result = "\x00";
is($function->Call(0xFF02, 0xFF03, $result), pack('c', -128), 'numeric truncation sum_char_ref() returns the expected value');
is(substr($result,0,1), "\x05", 'sum_char_ref() correctly modifies its ref argument');


#test old API WCHAR handing
$function = new Win32::API($test_dll, 'BOOL __stdcall wstr_cmp(LPWSTR string)');
is($function->Call(Encode::encode("UTF-16LE","Just another perl hacker\x00"))
   , 1, 'wstr_cmp() returns the expected value');


#test buffer overflow protection
$function = new Win32::API($test_dll, 'VOID __stdcall buffer_overflow(char* string)');
$input = "JAPH";
eval {
    $function->Call($input);
};
like($@, qr/.*\QWin32::API::Call: parameter 1 had a buffer overflow at\E.*/,
     "buffer overflow protection worked");

$ENV{WIN32_API_SORRY_I_WAS_AN_IDIOT} = 1 ;
$input = "JAPH";
$function->Call($input);
ok(1, "idiot flag works");

delete $ENV{WIN32_API_SORRY_I_WAS_AN_IDIOT};
#/* cdecl tests */

# Sum integers and double via _cdecl function
$function = new Win32::API($test_dll, 'int __cdecl c_sum_integers(int a, int b)');
ok(defined($function), "cdecl c_sum_integers() function defined");
is($function->Call(2, 3), 5, 'cdecl sum_integers() returns expected value');

$input = "Just another perl hacker";
$ptr = unpack('J', pack('p', $input));
$result = Win32::API::ReadMemory($ptr, length($input));
is($result,$input,'ReadMemory() works');

#test on old API that LPVOID is a char * and not a number/ptr
$function = new Win32::API($test_dll, 'BOOL __stdcall str_cmp(LPVOID string)');
is($function->Call("Just another perl hacker"), 1,
   'str_cmp() with LPVOID returns the expected value');


#test very high amounts of stack parameters, its intended for x64
$function = new Win32::API($test_dll, 'Take41Params', 'N' x 41, 'N');
is($function->Call(0..40), 1, #the C++ func was written using a perl script
   'Take41Params() returns the expected value');


__END__
#### 12: sum integers and double via _cdecl function
$function = new Win32::API($test_dll, 'int _cdecl c_call_sum_int(int a, int b)');
ok(defined($function), "_cdecl c_call_sum_int()");
is($function->Call(2, 3), 5);

#### 13: sum integers and double via _cdecl function
$function = new Win32::API($test_dll, 'int _cdecl c_call_sum_int_dbl(int a, double b)');
ok(defined($function), "_cdecl c_call_sum_int_dbl()");
is($function->Call(2, 3), 5);

#### 14: sum integers and double via _cdecl function, no prototype
$function = new Win32::API($test_dll, 'c_call_sum_int', 'II', 'I', '_cdecl');
ok(defined($function), "_cdecl c_call_sum_int()");
is($function->Call(2, 3), 5);

#### 15: sum 2 integers, no prototype
$function = new Win32::API($test_dll, 'sum_integers', 'II', 'I');
ok(defined($function), 'sum_integers()');
is($function->Call(2, 3), 5);

#### 16: convert integer to string
$function = new Win32::API($test_dll, 'int_to_str', 'IPI', 'I');
ok(defined($function), 'int_to_str()');
my $buf= " " x 16;
is( $function->Call(12345, $buf, length($buf)), 5 );
ok($buf =~ /^12345\x00 +$/);


