# -*- coding: utf-8 -*-

"""Test CLR method support."""

import System
import pytest
from Python.Test import MethodTest

def test_instance_method_overwritable():
    """Test instance method overwriting."""

    ob = MethodTest()
    ob.OverwritableMethod = lambda: "overwritten"
    assert ob.OverwritableMethod() == "overwritten"


def test_public_instance_method():
    """Test public instance method visibility."""
    ob = MethodTest()
    assert ob.PublicMethod() == "public"


def test_public_static_method():
    """Test public static method visibility."""
    ob = MethodTest()
    assert MethodTest.PublicStaticMethod() == "public static"
    assert ob.PublicStaticMethod() == "public static"


def test_protected_instance_method():
    """Test protected instance method visibility."""
    ob = MethodTest()
    assert ob.ProtectedMethod() == "protected"


def test_protected_static_method():
    """Test protected static method visibility."""
    ob = MethodTest()
    result = "protected static"
    assert MethodTest.ProtectedStaticMethod() == result
    assert ob.ProtectedStaticMethod() == result


def test_internal_method():
    """Test internal method visibility."""

    with pytest.raises(AttributeError):
        _ = MethodTest().InternalMethod

    with pytest.raises(AttributeError):
        _ = MethodTest.InternalMethod

    with pytest.raises(AttributeError):
        _ = MethodTest().InternalStaticMethod

    with pytest.raises(AttributeError):
        _ = MethodTest.InternalStaticMethod


def test_private_method():
    """Test private method visibility."""

    with pytest.raises(AttributeError):
        _ = MethodTest().PrivateMethod

    with pytest.raises(AttributeError):
        _ = MethodTest.PrivateMethod

    with pytest.raises(AttributeError):
        _ = MethodTest().PrivateStaticMethod

    with pytest.raises(AttributeError):
        _ = MethodTest.PrivateStaticMethod


def test_unbound_managed_method_call():
    """Test calling unbound managed methods."""
    from Python.Test import MethodTestSub

    ob = MethodTest()
    assert MethodTest.PublicMethod(ob) == "public"

    with pytest.raises(TypeError):
        MethodTest.PublicMethod()

    ob = MethodTestSub()
    assert MethodTestSub.PublicMethod(ob) == "public"
    assert MethodTestSub.PublicMethod(ob, "echo") == "echo"

    with pytest.raises(TypeError):
        MethodTestSub.PublicMethod("echo")


def test_overloaded_method_inheritance():
    """Test that overloads are inherited properly."""
    from Python.Test import MethodTestSub

    ob = MethodTest()
    assert ob.PublicMethod() == "public"

    with pytest.raises(TypeError):
        ob = MethodTest()
        ob.PublicMethod("echo")

    ob = MethodTestSub()
    assert ob.PublicMethod() == "public"

    assert ob.PublicMethod("echo") == "echo"


def test_method_descriptor_abuse():
    """Test method descriptor abuse."""
    desc = MethodTest.__dict__['PublicMethod']

    with pytest.raises(TypeError):
        desc.__get__(0, 0)

    with pytest.raises(AttributeError):
        desc.__set__(0, 0)


def test_method_docstrings():
    """Test standard method docstring generation"""
    method = MethodTest.GetType
    value = 'System.Type GetType()'
    assert method.__doc__ == value


# ======================================================================
# Tests of specific argument and result conversion scenarios
# ======================================================================
def test_method_call_enum_conversion():
    """Test enum conversion in method call."""
    from System import TypeCode

    ob = MethodTest()
    r = ob.TestEnumConversion(TypeCode.Int32)
    assert r == TypeCode.Int32


def test_method_call_flags_conversion():
    """Test flags conversion in method call."""
    from System.IO import FileAccess

    ob = MethodTest()
    flags = FileAccess.Read | FileAccess.Write
    r = ob.TestFlagsConversion(flags)
    assert r == flags


def test_method_call_struct_conversion():
    """Test struct conversion in method call."""
    from System import Guid

    ob = MethodTest()
    guid = Guid.NewGuid()
    temp = guid.ToString()
    r = ob.TestStructConversion(guid)
    assert r.ToString() == temp


def test_subclass_instance_conversion():
    """Test subclass instance conversion in method call."""

    class TestSubException(System.Exception):
        pass

    ob = MethodTest()
    instance = TestSubException()
    result = ob.TestSubclassConversion(instance)
    assert isinstance(result, System.Exception)


def test_null_array_conversion():
    """Test null array conversion in method call."""
    ob = MethodTest()
    r = ob.TestNullArrayConversion(None)
    assert r is None


def test_string_params_args():
    """Test use of string params."""
    result = MethodTest.TestStringParamsArg('one', 'two', 'three')
    assert result.Length == 4
    assert len(result) == 4, result
    assert result[0] == 'one'
    assert result[1] == 'two'
    assert result[2] == 'three'
    # ensures params string[] overload takes precedence over params object[]
    assert result[3] == 'tail'

    result = MethodTest.TestStringParamsArg(['one', 'two', 'three'])
    assert len(result) == 4
    assert result[0] == 'one'
    assert result[1] == 'two'
    assert result[2] == 'three'
    assert result[3] == 'tail'


def test_object_params_args():
    """Test use of object params."""
    result = MethodTest.TestObjectParamsArg('one', 'two', 'three')
    assert len(result) == 3, result
    assert result[0] == 'one'
    assert result[1] == 'two'
    assert result[2] == 'three'

    result = MethodTest.TestObjectParamsArg(['one', 'two', 'three'])
    assert len(result) == 3, result
    assert result[0] == 'one'
    assert result[1] == 'two'
    assert result[2] == 'three'


def test_value_params_args():
    """Test use of value type params."""
    result = MethodTest.TestValueParamsArg(1, 2, 3)
    assert len(result) == 3
    assert result[0] == 1
    assert result[1] == 2
    assert result[2] == 3

    result = MethodTest.TestValueParamsArg([1, 2, 3])
    assert len(result) == 3
    assert result[0] == 1
    assert result[1] == 2
    assert result[2] == 3


def test_non_params_array_in_last_place():
    """Test overload resolution with of non-"params" array as
    last parameter."""
    result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3)
    assert result

def test_params_methods_with_no_params():
    """Tests that passing no arguments to a params method
    passes an empty array"""
    result = MethodTest.TestValueParamsArg()
    assert len(result) == 0

    result = MethodTest.TestOneArgWithParams('Some String')
    assert len(result) == 0

    result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String')
    assert len(result) == 0

def test_params_methods_with_non_lists():
    """Tests that passing single parameters to a params
    method will convert into an array on the .NET side"""
    result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4])
    assert len(result) == 4

    result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4)
    assert len(result) == 4

    result = MethodTest.TestOneArgWithParams('Some String', [5])
    assert len(result) == 1

    result = MethodTest.TestOneArgWithParams('Some String', 5)
    assert len(result) == 1

def test_params_method_with_lists():
    """Tests passing multiple lists to a params object[] method"""
    result = MethodTest.TestObjectParamsArg([],[])
    assert len(result) == 2

def test_string_out_params():
    """Test use of string out-parameters."""
    result = MethodTest.TestStringOutParams("hi", "there")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"

    result = MethodTest.TestStringOutParams("hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"


def test_string_out_params_without_passing_string_value():
    """Test use of string out-parameters."""
    # @eirannejad 2022-01-13
    result = MethodTest.TestStringOutParams("hi")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"


def test_string_ref_params():
    """Test use of string byref parameters."""
    result = MethodTest.TestStringRefParams("hi", "there")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"

    result = MethodTest.TestStringRefParams("hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"


def test_value_out_params():
    """Test use of value type out-parameters."""
    result = MethodTest.TestValueOutParams("hi", 1)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == 42

    # None cannot be converted to a value type like int, long, etc.
    with pytest.raises(TypeError):
        MethodTest.TestValueOutParams("hi", None)


def test_value_out_params_without_passing_string_value():
    """Test use of string out-parameters."""
    # @eirannejad 2022-01-13
    result = MethodTest.TestValueOutParams("hi")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == 42


def test_value_ref_params():
    """Test use of value type byref parameters."""
    result = MethodTest.TestValueRefParams("hi", 1)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == 42

    # None cannot be converted to a value type like int, long, etc.
    with pytest.raises(TypeError):
        MethodTest.TestValueRefParams("hi", None)


def test_object_out_params():
    """Test use of object out-parameters."""
    result = MethodTest.TestObjectOutParams("hi", MethodTest())
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Exception)

    result = MethodTest.TestObjectOutParams("hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Exception)


def test_object_out_params_without_passing_string_value():
    """Test use of object out-parameters."""
    result = MethodTest.TestObjectOutParams("hi")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Exception)


def test_object_ref_params():
    """Test use of object byref parameters."""
    result = MethodTest.TestObjectRefParams("hi", MethodTest())
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Exception)

    result = MethodTest.TestObjectRefParams("hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Exception)


def test_struct_out_params():
    """Test use of struct out-parameters."""
    result = MethodTest.TestStructOutParams("hi", System.Guid.NewGuid())
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Guid)

    # None cannot be converted to a value type like a struct
    with pytest.raises(TypeError):
        MethodTest.TestValueRefParams("hi", None)


def test_struct_out_params_without_passing_string_value():
    """Test use of struct out-parameters."""
    result = MethodTest.TestStructOutParams("hi")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Guid)


def test_struct_ref_params():
    """Test use of struct byref parameters."""
    result = MethodTest.TestStructRefParams("hi", System.Guid.NewGuid())
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert isinstance(result[1], System.Guid)

    # None cannot be converted to a value type like a struct
    with pytest.raises(TypeError):
        MethodTest.TestValueRefParams("hi", None)


def test_void_single_out_param():
    """Test void method with single out-parameter."""
    result = MethodTest.TestVoidSingleOutParam(9)
    assert result == 42

    # None cannot be converted to a value type
    with pytest.raises(TypeError):
        MethodTest.TestVoidSingleOutParam(None)


def test_void_single_ref_param():
    """Test void method with single ref-parameter."""
    result = MethodTest.TestVoidSingleRefParam(9)
    assert result == 42

    # None cannot be converted to a value type
    with pytest.raises(TypeError):
        MethodTest.TestVoidSingleRefParam(None)


def test_single_default_param():
    """Test void method with single ref-parameter."""
    result = MethodTest.TestSingleDefaultParam()
    assert result == 5


def test_decimal_default_param():
    """Test that decimal default parameters work."""
    result = MethodTest.TestDecimalDefaultParam()
    assert result == System.Decimal(1)


def test_one_arg_and_two_default_param():
    """Test void method with single ref-parameter."""
    result = MethodTest.TestOneArgAndTwoDefaultParam(11)
    assert result == 22

    result = MethodTest.TestOneArgAndTwoDefaultParam(15)
    assert result == 26

    result = MethodTest.TestOneArgAndTwoDefaultParam(20)
    assert result == 31


def test_two_default_param():
    """Test void method with single ref-parameter."""
    result = MethodTest.TestTwoDefaultParam()
    assert result == 11


def test_explicit_selection_with_out_modifier():
    """Check explicit overload selection with out modifiers."""
    refstr = System.String("").GetType().MakeByRefType()
    result = MethodTest.TestStringOutParams.__overloads__[str, refstr](
        "hi", "there")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"

    result = MethodTest.TestStringOutParams.__overloads__[str, refstr](
        "hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"


def test_explicit_selection_with_ref_modifier():
    """Check explicit overload selection with ref modifiers."""
    refstr = System.String("").GetType().MakeByRefType()
    result = MethodTest.TestStringRefParams.__overloads__[str, refstr](
        "hi", "there")
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"

    result = MethodTest.TestStringRefParams.__overloads__[str, refstr](
        "hi", None)
    assert isinstance(result, tuple)
    assert len(result) == 2
    assert result[0] is True
    assert result[1] == "output string"


def test_explicit_overload_selection():
    """Check explicit overload selection using [] syntax."""
    from Python.Test import ISayHello1, InterfaceTest, ShortEnum
    from System import Array

    inst = InterfaceTest()

    value = MethodTest.Overloaded.__overloads__[System.Boolean](True)
    assert value is True

    value = MethodTest.Overloaded.__overloads__[bool](True)
    assert value is True

    value = MethodTest.Overloaded.__overloads__[System.Byte](255)
    assert value == 255

    value = MethodTest.Overloaded.__overloads__[System.SByte](127)
    assert value == 127

    value = MethodTest.Overloaded.__overloads__[System.Char](u'A')
    assert value == u'A'

    value = MethodTest.Overloaded.__overloads__[System.Char](65535)
    assert value == chr(65535)

    value = MethodTest.Overloaded.__overloads__[System.Int16](32767)
    assert value == 32767

    value = MethodTest.Overloaded.__overloads__[System.Int32](2147483647)
    assert value == 2147483647

    value = MethodTest.Overloaded.__overloads__[int](2147483647)
    assert value == 2147483647

    value = MethodTest.Overloaded.__overloads__[System.Int64](
        9223372036854775807
    )
    assert value == 9223372036854775807

    value = MethodTest.Overloaded.__overloads__[System.UInt16](65000)
    assert value == 65000

    value = MethodTest.Overloaded.__overloads__[System.UInt32](
        4294967295
    )
    assert value == 4294967295

    value = MethodTest.Overloaded.__overloads__[System.UInt64](
        18446744073709551615
    )
    assert value == 18446744073709551615

    value = MethodTest.Overloaded.__overloads__[System.Single](3.402823e38)
    assert value == System.Single(3.402823e38)

    value = MethodTest.Overloaded.__overloads__[System.Double](
        1.7976931348623157e308)
    assert value == 1.7976931348623157e308

    value = MethodTest.Overloaded.__overloads__[float](
        1.7976931348623157e308)
    assert value == 1.7976931348623157e308

    value = MethodTest.Overloaded.__overloads__[System.Decimal](
        System.Decimal.One)
    assert value == System.Decimal.One

    value = MethodTest.Overloaded.__overloads__[System.String]("spam")
    assert value == "spam"

    value = MethodTest.Overloaded.__overloads__[str]("spam")
    assert value == "spam"

    value = MethodTest.Overloaded.__overloads__[ShortEnum](ShortEnum.Zero)
    assert value == ShortEnum.Zero

    value = MethodTest.Overloaded.__overloads__[System.Object](inst)
    assert value.__class__ == inst.__class__

    value = MethodTest.Overloaded.__overloads__[InterfaceTest](inst)
    assert value.__class__ == inst.__class__

    iface_class = ISayHello1(InterfaceTest()).__class__
    value = MethodTest.Overloaded.__overloads__[ISayHello1](inst)
    assert value.__class__ != inst.__class__
    assert value.__class__ == iface_class

    atype = Array[System.Object]
    value = MethodTest.Overloaded.__overloads__[str, int, atype](
        "one", 1, atype([1, 2, 3]))
    assert value == 3

    value = MethodTest.Overloaded.__overloads__[str, int]("one", 1)
    assert value == 1

    value = MethodTest.Overloaded.__overloads__[int, str](1, "one")
    assert value == 1


def test_overload_selection_with_array_types():
    """Check overload selection using array types."""
    from Python.Test import ISayHello1, InterfaceTest, ShortEnum
    from System import Array

    inst = InterfaceTest()

    vtype = Array[System.Boolean]
    input_ = vtype([True, True])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] is True
    assert value[1] is True

    vtype = Array[bool]
    input_ = vtype([True, True])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] is True
    assert value[1] is True

    vtype = Array[System.Byte]
    input_ = vtype([0, 255])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 255

    vtype = Array[System.SByte]
    input_ = vtype([0, 127])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 127

    vtype = Array[System.Char]
    input_ = vtype([u'A', u'Z'])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == u'A'
    assert value[1] == u'Z'

    vtype = Array[System.Char]
    input_ = vtype([0, 65535])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == chr(0)
    assert value[1] == chr(65535)

    vtype = Array[System.Int16]
    input_ = vtype([0, 32767])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 32767

    vtype = Array[System.Int32]
    input_ = vtype([0, 2147483647])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 2147483647

    vtype = Array[int]
    input_ = vtype([0, 2147483647])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 2147483647

    vtype = Array[System.Int64]
    input_ = vtype([0, 9223372036854775807])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 9223372036854775807

    vtype = Array[System.UInt16]
    input_ = vtype([0, 65000])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 65000

    vtype = Array[System.UInt32]
    input_ = vtype([0, 4294967295])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 4294967295

    vtype = Array[System.UInt64]
    input_ = vtype([0, 18446744073709551615])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0
    assert value[1] == 18446744073709551615

    vtype = Array[System.Single]
    input_ = vtype([0.0, 3.402823e38])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0.0
    assert value[1] == System.Single(3.402823e38)

    vtype = Array[System.Double]
    input_ = vtype([0.0, 1.7976931348623157e308])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0.0
    assert value[1] == 1.7976931348623157e308

    vtype = Array[float]
    input_ = vtype([0.0, 1.7976931348623157e308])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == 0.0
    assert value[1] == 1.7976931348623157e308

    vtype = Array[System.Decimal]
    input_ = vtype([System.Decimal.Zero, System.Decimal.One])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == System.Decimal.Zero
    assert value[1] == System.Decimal.One

    vtype = Array[System.String]
    input_ = vtype(["one", "two"])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == "one"
    assert value[1] == "two"

    vtype = Array[str]
    input_ = vtype(["one", "two"])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == "one"
    assert value[1] == "two"

    vtype = Array[ShortEnum]
    input_ = vtype([ShortEnum.Zero, ShortEnum.One])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0] == ShortEnum.Zero
    assert value[1] == ShortEnum.One

    vtype = Array[System.Object]
    input_ = vtype([inst, inst])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0].__class__ == inst.__class__
    assert value[1].__class__ == inst.__class__

    vtype = Array[InterfaceTest]
    input_ = vtype([inst, inst])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0].__class__ == inst.__class__
    assert value[1].__class__ == inst.__class__

    iface_class = ISayHello1(inst).__class__
    vtype = Array[ISayHello1]
    input_ = vtype([inst, inst])
    value = MethodTest.Overloaded.__overloads__[vtype](input_)
    assert value[0].__class__ == iface_class
    assert value[1].__class__ == iface_class


def test_explicit_overload_selection_failure():
    """Check that overload selection fails correctly."""

    with pytest.raises(TypeError):
        _ = MethodTest.Overloaded.__overloads__[System.Type](True)

    with pytest.raises(TypeError):
        _ = MethodTest.Overloaded.__overloads__[int, int](1, 1)

    with pytest.raises(TypeError):
        _ = MethodTest.Overloaded.__overloads__[str, int, int]("", 1, 1)

    with pytest.raises(TypeError):
        _ = MethodTest.Overloaded.__overloads__[int, int](1)


def test_we_can_bind_to_encoding_get_string():
    """Check that we can bind to the Encoding.GetString method
    with variables."""
    from System.Text import Encoding
    from System.IO import MemoryStream

    my_bytes = Encoding.UTF8.GetBytes('Some testing string')
    stream = MemoryStream()
    stream.Write(my_bytes, 0, my_bytes.Length)
    stream.Position = 0

    buff = System.Array.CreateInstance(System.Byte, 3)
    buff.Initialize()
    data = []
    read = 1

    while read > 0:
        read = stream.Read(buff, 0, buff.Length)
        temp = Encoding.UTF8.GetString(buff, 0, read)
        data.append(temp)

    data = ''.join(data)
    assert data == 'Some testing string'


def test_wrong_overload():
    """Test regression in which implicit conversion caused the wrong types
    to be used. See #131 for issue. Fixed by #137, #151"""

    # Used to return `50L`
    res = System.Math.Abs(50.5)
    assert res == 50.5
    assert type(res) == float

    res = System.Math.Abs(-50.5)
    assert res == 50.5
    assert type(res) == float

    res = System.Math.Max(50.5, 50.1)
    assert res == 50.5
    assert type(res) == float

    res = System.Math.Max(System.Double(10.5), System.Double(50.5))
    assert res == 50.5
    assert type(res) == float  # Should it return a System.Double?

    res = System.Math.Max(System.Double(50.5), 50.1)
    assert res == 50.5
    assert type(res) == float


def test_no_object_in_param():
    """Test that fix for #203 doesn't break behavior w/ no object overload"""

    res = MethodTest.TestOverloadedNoObject(5)
    assert res == "Got int"

    res = MethodTest.TestOverloadedNoObject(i=7)
    assert res == "Got int"

    with pytest.raises(TypeError):
        MethodTest.TestOverloadedNoObject("test")

    with pytest.raises(TypeError):
        MethodTest.TestOverloadedNoObject(5.5)

    # Ensure that the top-level error is TypeError even if the inner error is an OverflowError
    with pytest.raises(TypeError):
        MethodTest.TestOverloadedNoObject(2147483648)

def test_object_in_param():
    """Test regression introduced by #151 in which Object method overloads
    aren't being used. See #203 for issue."""

    res = MethodTest.TestOverloadedObject(5)
    assert res == "Got int"

    res = MethodTest.TestOverloadedObject(i=7)
    assert res == "Got int"

    res = MethodTest.TestOverloadedObject("test")
    assert res == "Got object"

    res = MethodTest.TestOverloadedObject(o="test")
    assert res == "Got object"


def test_object_in_multiparam():
    """Test method with object multiparams behaves"""

    res = MethodTest.TestOverloadedObjectTwo(5, 5)
    assert res == "Got int-int"

    res = MethodTest.TestOverloadedObjectTwo(5, "foo")
    assert res == "Got int-string"

    res = MethodTest.TestOverloadedObjectTwo("foo", 7.24)
    assert res == "Got string-object"

    res = MethodTest.TestOverloadedObjectTwo("foo", "bar")
    assert res == "Got string-string"

    res = MethodTest.TestOverloadedObjectTwo("foo", 5)
    assert res == "Got string-int"

    res = MethodTest.TestOverloadedObjectTwo(7.24, 7.24)
    assert res == "Got object-object"

    res = MethodTest.TestOverloadedObjectTwo(a=5, b=5)
    assert res == "Got int-int"

    res = MethodTest.TestOverloadedObjectTwo(5, b=5)
    assert res == "Got int-int"

    res = MethodTest.TestOverloadedObjectTwo(a=5, b="foo")
    assert res == "Got int-string"

    res = MethodTest.TestOverloadedObjectTwo(5, b="foo")
    assert res == "Got int-string"

    res = MethodTest.TestOverloadedObjectTwo(a="foo", b=7.24)
    assert res == "Got string-object"

    res = MethodTest.TestOverloadedObjectTwo("foo", b=7.24)
    assert res == "Got string-object"

    res = MethodTest.TestOverloadedObjectTwo(a="foo", b="bar")
    assert res == "Got string-string"

    res = MethodTest.TestOverloadedObjectTwo("foo", b="bar")
    assert res == "Got string-string"

    res = MethodTest.TestOverloadedObjectTwo(a="foo", b=5)
    assert res == "Got string-int"

    res = MethodTest.TestOverloadedObjectTwo("foo", b=5)
    assert res == "Got string-int"

    res = MethodTest.TestOverloadedObjectTwo(a=7.24, b=7.24)
    assert res == "Got object-object"

    res = MethodTest.TestOverloadedObjectTwo(7.24, b=7.24)
    assert res == "Got object-object"


def test_object_in_multiparam_exception():
    """Test method with object multiparams behaves"""

    with pytest.raises(TypeError) as excinfo:
        MethodTest.TestOverloadedObjectThree("foo", "bar")

    e = excinfo.value
    c = e.__cause__
    assert c.GetType().FullName == 'System.AggregateException'
    assert len(c.InnerExceptions) == 2

def test_case_sensitive():
    """Test that case-sensitivity is respected. GH#81"""

    res = MethodTest.CaseSensitive()
    assert res == "CaseSensitive"

    res = MethodTest.Casesensitive()
    assert res == "Casesensitive"

    with pytest.raises(AttributeError):
        MethodTest.casesensitive()

def test_getting_generic_method_binding_does_not_leak_ref_count():
    """Test that managed object is freed after calling generic method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import sys

    refCount = sys.getrefcount(PlainOldClass().GenericMethod[str])
    assert refCount == 1

def test_getting_generic_method_binding_does_not_leak_memory():
    """Test that managed object is freed after calling generic method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import psutil, os, gc, clr

    process = psutil.Process(os.getpid())
    processBytesBeforeCall = process.memory_info().rss
    print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall))

    iterations = 500
    for i in range(iterations):
        PlainOldClass().GenericMethod[str]

    gc.collect()
    System.GC.Collect()

    processBytesAfterCall = process.memory_info().rss
    print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall))
    processBytesDelta = processBytesAfterCall - processBytesBeforeCall
    print("Memory delta: " + str(processBytesDelta))

    bytesAllocatedPerIteration = pow(2, 20)  # 1MB
    bytesLeakedPerIteration = processBytesDelta / iterations

    # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration
    failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2

    assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration

def test_getting_overloaded_method_binding_does_not_leak_ref_count():
    """Test that managed object is freed after calling overloaded method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import sys

    refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int])
    assert refCount == 1

def test_getting_overloaded_method_binding_does_not_leak_memory():
    """Test that managed object is freed after calling overloaded method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import psutil, os, gc, clr

    process = psutil.Process(os.getpid())
    processBytesBeforeCall = process.memory_info().rss
    print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall))

    iterations = 500
    for i in range(iterations):
        PlainOldClass().OverloadedMethod.Overloads[int]

    gc.collect()
    System.GC.Collect()

    processBytesAfterCall = process.memory_info().rss
    print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall))
    processBytesDelta = processBytesAfterCall - processBytesBeforeCall
    print("Memory delta: " + str(processBytesDelta))

    bytesAllocatedPerIteration = pow(2, 20)  # 1MB
    bytesLeakedPerIteration = processBytesDelta / iterations

    # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration
    failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2

    assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration

def test_getting_method_overloads_binding_does_not_leak_ref_count():
    """Test that managed object is freed after calling overloaded method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import sys

    refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads)
    assert refCount == 1

@pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False)
def test_getting_method_overloads_binding_does_not_leak_memory():
    """Test that managed object is freed after calling overloaded method. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import psutil, os, gc, clr

    process = psutil.Process(os.getpid())
    processBytesBeforeCall = process.memory_info().rss
    print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall))

    iterations = 500
    for i in range(iterations):
        PlainOldClass().OverloadedMethod.Overloads

    gc.collect()
    System.GC.Collect()

    processBytesAfterCall = process.memory_info().rss
    print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall))
    processBytesDelta = processBytesAfterCall - processBytesBeforeCall
    print("Memory delta: " + str(processBytesDelta))

    bytesAllocatedPerIteration = pow(2, 20)  # 1MB
    bytesLeakedPerIteration = processBytesDelta / iterations

    # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration
    failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2

    assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration

def test_getting_overloaded_constructor_binding_does_not_leak_ref_count():
    """Test that managed object is freed after calling overloaded constructor, constructorbinding.cs mp_subscript. Issue #691"""

    from PlainOldNamespace import PlainOldClass

    import sys

    # simple test
    refCount = sys.getrefcount(PlainOldClass.Overloads[int])
    assert refCount == 1


def test_default_params():
    # all positional parameters
    res = MethodTest.DefaultParams(1,2,3,4)
    assert res == "1234"

    res = MethodTest.DefaultParams(1, 2, 3)
    assert res == "1230"

    res = MethodTest.DefaultParams(1, 2)
    assert res == "1200"

    res = MethodTest.DefaultParams(1)
    assert res == "1000"

    res = MethodTest.DefaultParams(a=2)
    assert res == "2000"

    res = MethodTest.DefaultParams(b=3)
    assert res == "0300"

    res = MethodTest.DefaultParams(c=4)
    assert res == "0040"

    res = MethodTest.DefaultParams(d=7)
    assert res == "0007"

    res = MethodTest.DefaultParams(a=2, c=5)
    assert res == "2050"

    res = MethodTest.DefaultParams(1, d=7, c=3)
    assert res == "1037"

    with pytest.raises(TypeError):
        MethodTest.DefaultParams(1,2,3,4,5)

def test_optional_params():
    res = MethodTest.OptionalParams(1, 2, 3, 4)
    assert res == "1234"

    res = MethodTest.OptionalParams(1, 2, 3)
    assert res == "1230"

    res = MethodTest.OptionalParams(1, 2)
    assert res == "1200"

    res = MethodTest.OptionalParams(1)
    assert res == "1000"

    res = MethodTest.OptionalParams(a=2)
    assert res == "2000"

    res = MethodTest.OptionalParams(b=3)
    assert res == "0300"

    res = MethodTest.OptionalParams(c=4)
    assert res == "0040"

    res = MethodTest.OptionalParams(d=7)
    assert res == "0007"

    res = MethodTest.OptionalParams(a=2, c=5)
    assert res == "2050"

    res = MethodTest.OptionalParams(1, d=7, c=3)
    assert res == "1037"

    res = MethodTest.OptionalParams_TestMissing()
    assert res == True

    res = MethodTest.OptionalParams_TestMissing(None)
    assert res == False

    res = MethodTest.OptionalParams_TestMissing(a = None)
    assert res == False

    res = MethodTest.OptionalParams_TestMissing(a='hi')
    assert res == False

    res = MethodTest.OptionalParams_TestReferenceType()
    assert res == True

    res = MethodTest.OptionalParams_TestReferenceType(None)
    assert res == True

    res = MethodTest.OptionalParams_TestReferenceType(a=None)
    assert res == True

    res = MethodTest.OptionalParams_TestReferenceType('hi')
    assert res == False

    res = MethodTest.OptionalParams_TestReferenceType(a='hi')
    assert res == False

def test_optional_and_default_params():

    res = MethodTest.OptionalAndDefaultParams()
    assert res == "0000"

    res = MethodTest.OptionalAndDefaultParams(1)
    assert res == "1000"

    res = MethodTest.OptionalAndDefaultParams(1, c=4)
    assert res == "1040"

    res = MethodTest.OptionalAndDefaultParams(b=4, c=7)
    assert res == "0470"

    res = MethodTest.OptionalAndDefaultParams2()
    assert res == "0012"

    res = MethodTest.OptionalAndDefaultParams2(a=1,b=2,c=3,d=4)
    assert res == "1234"

    res = MethodTest.OptionalAndDefaultParams2(b=2, c=3)
    assert res == "0232"

def test_default_params_overloads():
    res = MethodTest.DefaultParamsWithOverloading(1, 2)
    assert res == "12"

    res = MethodTest.DefaultParamsWithOverloading(b=5)
    assert res == "25"

    res = MethodTest.DefaultParamsWithOverloading("d")
    assert res == "dbX"

    res = MethodTest.DefaultParamsWithOverloading(b="c")
    assert res == "acX"

    res = MethodTest.DefaultParamsWithOverloading(c=3)
    assert res == "013XX"

    res = MethodTest.DefaultParamsWithOverloading(5, c=2)
    assert res == "512XX"

    res = MethodTest.DefaultParamsWithOverloading(c=0, d=1)
    assert res == "5601XXX"

    res = MethodTest.DefaultParamsWithOverloading(1, d=1)
    assert res == "1671XXX"

def test_default_params_overloads_ambiguous_call():
    with pytest.raises(TypeError):
        MethodTest.DefaultParamsWithOverloading()

def test_keyword_arg_method_resolution():
    from Python.Test import MethodArityTest

    ob = MethodArityTest()
    assert ob.Foo(1, b=2) == "Arity 2"

def test_params_array_overload():
    res = MethodTest.ParamsArrayOverloaded()
    assert res == "without params-array"

    res = MethodTest.ParamsArrayOverloaded(1)
    assert res == "without params-array"

    res = MethodTest.ParamsArrayOverloaded(i=1)
    assert res == "without params-array"

    res = MethodTest.ParamsArrayOverloaded(1, 2)
    assert res == "with params-array"

    res = MethodTest.ParamsArrayOverloaded(1, 2, 3)
    assert res == "with params-array"

    res = MethodTest.ParamsArrayOverloaded(1, paramsArray=[])
    assert res == "with params-array"

    res = MethodTest.ParamsArrayOverloaded(1, i=1)
    assert res == "with params-array"

    res = MethodTest.ParamsArrayOverloaded(1, 2, 3, i=1)
    assert res == "with params-array"

@pytest.mark.skip(reason="FIXME: incorrectly failing")
def test_params_array_overloaded_failing():
    res = MethodTest.ParamsArrayOverloaded(1, 2, i=1)
    assert res == "with params-array"

    res = MethodTest.ParamsArrayOverloaded(paramsArray=[], i=1)
    assert res == "with params-array"

def test_method_encoding():
    MethodTest.EncodingTestÅngström()

def test_method_with_pointer_array_argument():
    with pytest.raises(TypeError):
        MethodTest.PointerArray([0])

def test_method_call_implicit_conversion():

    class IntAnswerMixin:
        # For Python >= 3.8
        def __index__(self):
            return 42

        # For Python < 3.10
        def __int__(self):
            return 42

    class Answer(int, IntAnswerMixin):
        pass

    class FloatAnswer(float, IntAnswerMixin):
        def __float__(self):
            return 42.0

    # TODO: This should also work for integer types but due to some complexities
    # in the C-API functions (some call __int__/__index__, some don't), it's not
    # supported, yet.
    for v in [Answer(), FloatAnswer()]:
        for t in [System.Double, System.Single]:
            min_value = t(t.MinValue)
            compare_to = min_value.CompareTo.__overloads__[t]

            assert compare_to(v) == -1

    class SomeNonFloat:
        def __float__(self):
            return 42.0

    for t in [System.Double, System.Single]:
        with pytest.raises(TypeError):
            min_value = t(t.MinValue)
            compare_to = min_value.CompareTo.__overloads__[t]
            assert compare_to(SomeNonFloat()) == -1
