"""
The purpose of this test file is to check how ctypes really work,
down to what aliases what and what exact types operations return.
"""

import pytest
from ctypes import *

def test_primitive_pointer():
    x = c_int(5)
    assert x.value == 5
    x.value = 6
    assert x.value == 6

    p = pointer(x)                           #  p ---> x = 6
    assert isinstance(p.contents, c_int)
    p.contents.value += 1
    assert x.value == 7                      #  p ---> x = 7

    y = c_int(12)
    p.contents = y                           #  p ---> y = 12
    p.contents.value += 2                    #  p ---> y = 14
    assert y.value == 14
    assert x.value == 7

    pp = pointer(p)                          #  pp ---> p ---> y = 14
    pp.contents.contents = x                 #  pp ---> p ---> x = 7
    p.contents.value += 2                    #  pp ---> p ---> x = 9
    assert x.value == 9

    assert isinstance(p[0], int)
    p[0] += 1                                #  pp ---> p ---> x = 10
    assert x.value == 10
    z = c_int(86)
    p[0] = z                                 #  pp ---> p ---> x = 86  (not z!)
    assert x.value == 86
    z.value = 84
    assert x.value == 86

    assert isinstance(pp[0], POINTER(c_int))
    assert pp[0].contents.value == x.value == 86
    pp[0].contents = z                       #  pp ---> p ---> z = 84
    assert p.contents.value == z.value == 84

##    *** the rest is commented out because it should work but occasionally
##    *** trigger a ctypes bug (SourceForge bug #1467852). ***
##    q = pointer(y)
##    pp[0] = q                                #  pp ---> p ---> y = 14
##    assert y.value == 14                     #        (^^^ not q! )
##    assert p.contents.value == 14
##    assert pp.contents.contents.value == 14
##    q.contents = x
##    assert pp.contents.contents.value == 14


def test_char_p():
    x = c_char_p(b"hello\x00world")
    assert x.value == b"hello"
    x.value = b"world"
    assert x.value == b"world"

    p = pointer(x)
    assert p[0] == x.value == b"world"
    p[0] = b"other"
    assert x.value == p.contents.value == p[0] == b"other"

    myarray = (c_char_p * 10)()
    myarray[7] = b"hello"
    assert isinstance(myarray[7], str)
    assert myarray[7] == b"hello"

def test_struct():
    class tagpoint(Structure):
        _fields_ = [('x', c_int),
                    ('p', POINTER(c_short))]

    y = c_short(123)
    z = c_short(-33)
    s = tagpoint()
    s.p.contents = z
    assert s.p.contents.value == -33
    s.p = pointer(y)
    assert s.p.contents.value == 123
    s.p.contents.value = 124
    assert y.value == 124

    s = tagpoint(x=12)
    assert s.x == 12
    s = tagpoint(17, p=pointer(z))
    assert s.x == 17
    assert s.p.contents.value == -33

def test_ptr_array():
    a = (POINTER(c_ushort) * 5)()
    x = c_ushort(52)
    y = c_ushort(1000)

    a[2] = pointer(x)
    assert a[2].contents.value == 52
    a[2].contents.value += 1
    assert x.value == 53

    a[3].contents = y
    assert a[3].contents.value == 1000
    a[3].contents.value += 1
    assert y.value == 1001

def test_void_p():
    x = c_int(12)
    p1 = cast(pointer(x), c_void_p)
    p2 = cast(p1, POINTER(c_int))
    assert p2.contents.value == 12

def test_char_array():
    a = (c_char * 3)()
    a[0] = b'x'
    a[1] = b'y'
    assert a.value == b'xy'
    a[2] = b'z'
    assert a.value == b'xyz'

    b = create_string_buffer(3)
    assert type(b) is type(a)
    assert len(b) == 3

    b.value = b"nxw"
    assert b[0] == b'n'
    assert b[1] == b'x'
    assert b[2] == b'w'

    b.value = b"?"
    assert b[0] == b'?'
    assert b[1] == b'\x00'
    assert b[2] == b'w'

    class S(Structure):
        _fields_ = [('p', POINTER(c_char))]

    s = S()
    s.p = b
    s.p.contents.value = b'!'
    assert b.value == b'!'

    assert len(create_string_buffer(0)) == 0

def test_array():
    a = (c_int * 10)()

    class S(Structure):
        _fields_ = [('p', POINTER(c_int))]

    s = S()
    s.p = a
    s.p.contents.value = 42
    assert a[0] == 42

    a = (c_int * 5)(5, 6, 7)
    assert list(a) == [5, 6, 7, 0, 0]

def test_truth_value():
    p = POINTER(c_int)()
    assert not p
    p.contents = c_int(12)
    assert p
    # I can't figure out how to reset p to NULL...

    assert c_int(12)
    assert not c_int(0)    # a bit strange, if you ask me
    assert c_int(-1)
    assert not c_byte(0)
    assert not c_char(b'\x00')   # hum
    assert not c_float(0.0)
    assert not c_double(0.0)
    assert not c_ulonglong(0)
    assert c_ulonglong(2L**42)

    assert c_char_p("hello")
    assert c_char_p("")
    assert not c_char_p(None)

    assert not c_void_p()

def test_sizeof():
    x = create_string_buffer(117)
    assert sizeof(x) == 117    # assumes that chars are one byte each
    x = (c_int * 42)()
    assert sizeof(x) == 42 * sizeof(c_int)

def test_convert_pointers(dll):
    func = dll._testfunc_p_p
    func.restype = c_char_p

    # automatic conversions to c_char_p
    func.argtypes = [c_char_p]
    assert func(b"hello") == b"hello"
    assert func(c_char_p(b"hello")) == b"hello"
    assert func((c_char * 6)(*b"hello")) == b"hello"
    assert func(create_string_buffer(b"hello")) == b"hello"

    # automatic conversions to c_void_p
    func.argtypes = [c_void_p]
    assert func(b"hello") == b"hello"
    assert func(c_char_p(b"hello")) == b"hello"
    assert func((c_char * 6)(*b"hello")) == b"hello"
    assert func((c_byte * 6)(104,101,108,108,111)) ==b"hello"
    assert func(create_string_buffer(b"hello")) == b"hello"

def test_varsize_cast():
    import struct
    N = struct.calcsize("l")
    x = c_long()
    p = cast(pointer(x), POINTER(c_ubyte*N))
    for i, c in enumerate(struct.pack("l", 12345678)):
        p.contents[i] = ord(c)
    assert x.value == 12345678

def test_cfunctype_inspection():
    T = CFUNCTYPE(c_int, c_ubyte)
    # T.argtypes and T.restype don't work, must use a dummy instance
    assert list(T().argtypes) == [c_ubyte]
    assert T().restype == c_int

def test_from_param():
    # other working cases of from_param
    assert isinstance(c_void_p.from_param((c_int * 4)()), c_int * 4)

def test_array_mul():
    assert c_int * 10 == 10 * c_int == c_int * 10L == 10L * c_int
    with pytest.raises(TypeError):
        c_int * c_int
    with pytest.raises(TypeError):
        c_int * (-1.5)
    with pytest.raises(TypeError):
        c_int * "foo"
    with pytest.raises(TypeError):
        (-1.5) * c_int
    with pytest.raises(TypeError):
        "foo" * c_int

def test_cast_none():
    def check(P):
        x = cast(None, P)
        assert isinstance(x, P)
        assert not x
    check(c_void_p)
    check(c_char_p)
    check(POINTER(c_int))
    check(POINTER(c_int * 10))
