import py
import sys, os, unittest
from ctypes import *

try:
    WINFUNCTYPE
except NameError:
    # fake to enable this test on Linux
    WINFUNCTYPE = CFUNCTYPE

def setup_module(mod):
    import conftest
    _ctypes_test = str(conftest.sofile)
    mod.lib = CDLL(_ctypes_test)


class TestCFuncPtr:
    def test_basic(self):
        X = WINFUNCTYPE(c_int, c_int, c_int)

        def func(*args):
            return len(args)

        x = X(func)
        assert x.restype == c_int
        assert x.argtypes == (c_int, c_int)
        assert sizeof(x) == sizeof(c_voidp)
        assert sizeof(X) == sizeof(c_voidp)

    def test_first(self):
        StdCallback = WINFUNCTYPE(c_int, c_int, c_int)
        CdeclCallback = CFUNCTYPE(c_int, c_int, c_int)

        def func(a, b):
            return a + b

        s = StdCallback(func)
        c = CdeclCallback(func)

        assert s(1, 2) == 3
        assert c(1, 2) == 3
        # The following no longer raises a TypeError - it is now
        # possible, as in C, to call cdecl functions with more parameters.
        #self.assertRaises(TypeError, c, 1, 2, 3)
        py.test.skip("cdecl funcptrs ignoring extra args is not implemented")
        assert c(1, 2, 3, 4, 5, 6) == 3
        if not WINFUNCTYPE is CFUNCTYPE and os.name != "ce":
            raises(TypeError, s, 1, 2, 3)

    def test_structures(self):
        if sys.platform != 'win32':
            py.test.skip("win32 related")
        WNDPROC = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int)

        def wndproc(hwnd, msg, wParam, lParam):
            return hwnd + msg + wParam + lParam

        HINSTANCE = c_int
        HICON = c_int
        HCURSOR = c_int
        LPCTSTR = c_char_p

        class WNDCLASS(Structure):
            _fields_ = [("style", c_uint),
                        ("lpfnWndProc", WNDPROC),
                        ("cbClsExtra", c_int),
                        ("cbWndExtra", c_int),
                        ("hInstance", HINSTANCE),
                        ("hIcon", HICON),
                        ("hCursor", HCURSOR),
                        ("lpszMenuName", LPCTSTR),
                        ("lpszClassName", LPCTSTR)]

        wndclass = WNDCLASS()
        wndclass.lpfnWndProc = WNDPROC(wndproc)

        WNDPROC_2 = WINFUNCTYPE(c_long, c_int, c_int, c_int, c_int)

        # This is no longer true, now that WINFUNCTYPE caches created types internally.
        ## # CFuncPtr subclasses are compared by identity, so this raises a TypeError:
        ## raises(TypeError, setattr, wndclass,
        ##                  "lpfnWndProc", WNDPROC_2(wndproc))
        # instead:

        assert WNDPROC is WNDPROC_2
        # 'wndclass.lpfnWndProc' leaks 94 references.  Why?
        assert wndclass.lpfnWndProc(1, 2, 3, 4) == 10


        f = wndclass.lpfnWndProc

        del wndclass
        del wndproc

        assert f(10, 11, 12, 13) == 46

    def test_dllfunctions(self):

        def NoNullHandle(value):
            if not value:
                raise WinError()
            return value

        strchr = lib.my_strchr
        strchr.restype = c_char_p
        strchr.argtypes = (c_char_p, c_char)
        assert strchr("abcdefghi", "b") == "bcdefghi"
        assert strchr("abcdefghi", "x") == None


        strtok = lib.my_strtok
        strtok.restype = c_char_p
        # Neither of this does work: strtok changes the buffer it is passed
##        strtok.argtypes = (c_char_p, c_char_p)
##        strtok.argtypes = (c_string, c_char_p)

        def c_string(init):
            size = len(init) + 1
            return (c_char*size)(*init)

        s = "a\nb\nc"
        b = c_string(s)

##        b = (c_char * (len(s)+1))()
##        b.value = s

##        b = c_string(s)
        assert strtok(b, "\n") == "a"
        assert strtok(None, "\n") == "b"
        assert strtok(None, "\n") == "c"
        assert strtok(None, "\n") == None

    def test_from_address(self):
        py.test.skip("This test needs mmap to make sure the"
                     " code is executable, please rewrite me")
        def make_function():
            proto = CFUNCTYPE(c_int)
            a=create_string_buffer(
                "\xB8\x78\x56\x34\x12" # mov eax, 0x12345678
                "\xc3"                 # ret 0
                )
            ptr = pointer(a)
            func = proto.from_address(addressof(ptr))
            func.__keep = ptr # keep ptr alive
            return func
        f = make_function()
        # This assembler should also work on Linux 32bit,
        # but it segfaults for some reason.
        if sys.platform == 'win32':
            assert f() == 0x12345678

    def test_restype(self):
        foo = lib.my_unused_function
        assert foo.restype is c_int     # by default
