# -*- coding: utf-8 -*-
# FIXME: This test module randomly passes/fails even if all tests are skipped.
# Something fishy is going on with the Test fixtures. Behavior seen on CI on
# both Linux and Windows
# TODO: Remove delay of class creations. Adding SetUp/TearDown may help

"""Test sub-classing managed types"""

import System
import pytest
from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest,
                         FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1)
from System.Collections.Generic import List


def interface_test_class_fixture(subnamespace):
    """Delay creation of class until test starts."""

    class InterfaceTestClass(IInterfaceTest):
        """class that implements the test interface"""
        __namespace__ = "Python.Test." + subnamespace

        def foo(self):
            return "InterfaceTestClass"

        def bar(self, x, i):
            return "/".join([x] * i)

    return InterfaceTestClass


def interface_generic_class_fixture(subnamespace):

    class GenericInterfaceImpl(IGenericInterface[int]):
        __namespace__ = "Python.Test." + subnamespace

        def Get(self, x):
            return x

    return GenericInterfaceImpl


def derived_class_fixture(subnamespace):
    """Delay creation of class until test starts."""

    class DerivedClass(SubClassTest):
        """class that derives from a class deriving from IInterfaceTest"""
        __namespace__ = "Python.Test." + subnamespace

        def foo(self):
            return "DerivedClass"

        def base_foo(self):
            return SubClassTest.foo(self)

        def super_foo(self):
            return super(DerivedClass, self).foo()

        def bar(self, x, i):
            return "_".join([x] * i)

        def return_list(self):
            l = List[str]()
            l.Add("A")
            l.Add("B")
            l.Add("C")
            return l

    return DerivedClass

def broken_derived_class_fixture(subnamespace):
    """Delay creation of class until test starts."""

    class DerivedClass(SubClassTest):
        """class that derives from a class deriving from IInterfaceTest"""
        __namespace__ = 3

    return DerivedClass

def derived_event_test_class_fixture(subnamespace):
    """Delay creation of class until test starts."""

    class DerivedEventTest(IInterfaceTest):
        """class that implements IInterfaceTest.TestEvent"""
        __namespace__ = "Python.Test." + subnamespace

        def __init__(self):
            self.event_handlers = []

        # event handling
        def add_TestEvent(self, handler):
            self.event_handlers.append(handler)

        def remove_TestEvent(self, handler):
            self.event_handlers.remove(handler)

        def OnTestEvent(self, value):
            args = EventArgsTest(value)
            for handler in self.event_handlers:
                handler(self, args)

    return DerivedEventTest


def test_base_class():
    """Test base class managed type"""
    ob = SubClassTest()
    assert ob.foo() == "foo"
    assert FunctionsTest.test_foo(ob) == "foo"
    assert ob.bar("bar", 2) == "bar"
    assert FunctionsTest.test_bar(ob, "bar", 2) == "bar"
    assert ob.not_overriden() == "not_overriden"
    assert list(ob.return_list()) == ["a", "b", "c"]
    assert list(SubClassTest.test_list(ob)) == ["a", "b", "c"]


def test_interface():
    """Test python classes can derive from C# interfaces"""
    InterfaceTestClass = interface_test_class_fixture(test_interface.__name__)
    ob = InterfaceTestClass()
    assert ob.foo() == "InterfaceTestClass"
    assert FunctionsTest.test_foo(ob) == "InterfaceTestClass"
    assert ob.bar("bar", 2) == "bar/bar"
    assert FunctionsTest.test_bar(ob, "bar", 2) == "bar/bar"

    # pass_through will convert from InterfaceTestClass -> IInterfaceTest,
    # causing a new wrapper object to be created. Hence id will differ.
    x = FunctionsTest.pass_through_interface(ob)
    assert id(x) != id(ob)


def test_derived_class():
    """Test python class derived from managed type"""
    DerivedClass = derived_class_fixture(test_derived_class.__name__)
    ob = DerivedClass()
    assert ob.foo() == "DerivedClass"
    assert ob.base_foo() == "foo"
    assert ob.super_foo() == "foo"
    assert FunctionsTest.test_foo(ob) == "DerivedClass"
    assert ob.bar("bar", 2) == "bar_bar"
    assert FunctionsTest.test_bar(ob, "bar", 2) == "bar_bar"
    assert ob.not_overriden() == "not_overriden"
    assert list(ob.return_list()) == ["A", "B", "C"]
    assert list(SubClassTest.test_list(ob)) == ["A", "B", "C"]

    x = FunctionsTest.pass_through(ob)
    assert id(x) == id(ob)

def test_broken_derived_class():
    """Test python class derived from managed type with invalid namespace"""
    with pytest.raises(TypeError):
        DerivedClass = broken_derived_class_fixture(test_derived_class.__name__)
        ob = DerivedClass()

def test_derived_traceback():
    """Test python exception traceback in class derived from managed base"""
    class DerivedClass(SubClassTest):
        __namespace__ = "Python.Test.traceback"

        def foo(self):
            print (xyzname)
            return None

    import sys,traceback
    ob = DerivedClass()

    # direct call
    try:
        ob.foo()
        assert False
    except:
        e = sys.exc_info()
    assert "xyzname" in str(e[1])
    location = traceback.extract_tb(e[2])[-1]
    assert location[2] == "foo"

    # call through managed code
    try:
        FunctionsTest.test_foo(ob)
        assert False
    except:
        e = sys.exc_info()
    assert "xyzname" in str(e[1])
    location = traceback.extract_tb(e[2])[-1]
    assert location[2] == "foo"


def test_create_instance():
    """Test derived instances can be created from managed code"""
    DerivedClass = derived_class_fixture(test_create_instance.__name__)
    ob = FunctionsTest.create_instance(DerivedClass)
    assert ob.foo() == "DerivedClass"
    assert FunctionsTest.test_foo(ob) == "DerivedClass"
    assert ob.bar("bar", 2) == "bar_bar"
    assert FunctionsTest.test_bar(ob, "bar", 2) == "bar_bar"
    assert ob.not_overriden() == "not_overriden"

    x = FunctionsTest.pass_through(ob)
    assert id(x) == id(ob)

    InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__)
    ob2 = FunctionsTest.create_instance_interface(InterfaceTestClass)
    assert ob2.foo() == "InterfaceTestClass"
    assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass"
    assert ob2.bar("bar", 2) == "bar/bar"
    assert FunctionsTest.test_bar(ob2, "bar", 2) == "bar/bar"

    y = FunctionsTest.pass_through_interface(ob2)
    assert id(y) != id(ob2)


def test_events():
    class EventHandler(object):
        def handler(self, x, args):
            self.value = args.value

    event_handler = EventHandler()

    x = SubClassTest()
    x.TestEvent += event_handler.handler
    assert FunctionsTest.test_event(x, 1) == 1
    assert event_handler.value == 1

    InterfaceTestClass = interface_test_class_fixture(test_events.__name__)
    i = InterfaceTestClass()
    with pytest.raises(System.NotImplementedException):
        FunctionsTest.test_event(i, 2)

    DerivedEventTest = derived_event_test_class_fixture(test_events.__name__)
    d = DerivedEventTest()
    d.add_TestEvent(event_handler.handler)
    assert FunctionsTest.test_event(d, 3) == 3
    assert event_handler.value == 3
    assert len(d.event_handlers) == 1


def test_isinstance_check():
    a = [str(x) for x in range(0, 1000)]
    b = [System.String(x) for x in a]

    for x in a:
        assert not isinstance(x, System.Object)
        assert not isinstance(x, System.String)

    for x in b:
        assert isinstance(x, System.Object)
        assert isinstance(x, System.String)

def test_namespace_and_init():
    calls = []
    class TestX(System.Object):
        __namespace__ = "test_clr_subclass_with_init_args"
        def __init__(self, *args, **kwargs):
            calls.append((args, kwargs))
    t = TestX(1,2,3,foo="bar")
    assert len(calls) == 1
    assert calls[0][0] == (1,2,3)
    assert calls[0][1] == {"foo":"bar"}

def test_namespace_and_argless_init():
    calls = []
    class TestX(System.Object):
        __namespace__ = "test_clr_subclass_without_init_args"
        def __init__(self):
            calls.append(True)
    t = TestX()
    assert len(calls) == 1
    assert calls[0] == True


def test_namespace_and_no_init():
    class TestX(System.Object):
        __namespace__ = "test_clr_subclass_without_init"
        q = 1
    t = TestX()
    assert t.q == 1

def test_construction_from_clr():
    import clr
    calls = []
    class TestX(System.Object):
        __namespace__ = "test_clr_subclass_init_from_clr"
        @clr.clrmethod(None, [int, str])
        def __init__(self, i, s):
            calls.append((i, s))

    # Construct a TestX from Python
    t = TestX(1, "foo")
    assert len(calls) == 1
    assert calls[0][0] == 1
    assert calls[0][1] == "foo"

    # Reset calls and construct a TestX from CLR
    calls = []
    tp = t.GetType()
    t2 = tp.GetConstructors()[0].Invoke(None)
    assert len(calls) == 0

    # The object has only been constructed, now it needs to be initialized as well
    tp.GetMethod("__init__").Invoke(t2, [1, "foo"])
    assert len(calls) == 1
    assert calls[0][0] == 1
    assert calls[0][1] == "foo"

# regression test for https://github.com/pythonnet/pythonnet/issues/1565
def test_can_be_collected_by_gc():
    from Python.Test import BaseClass

    class Derived(BaseClass):
        __namespace__ = 'test_can_be_collected_by_gc'

    inst = Derived()
    cycle = [inst]
    del inst
    cycle.append(cycle)
    del cycle

    import gc
    gc.collect()

def test_generic_interface():
    from System import Int32
    from Python.Test import GenericInterfaceUser, SpecificInterfaceUser

    GenericInterfaceImpl = interface_generic_class_fixture(test_generic_interface.__name__)

    obj = GenericInterfaceImpl()
    SpecificInterfaceUser(obj, Int32(0))
    GenericInterfaceUser[Int32](obj, Int32(0))

def test_virtual_generic_method():
    class OverloadingSubclass(GenericVirtualMethodTest):
        __namespace__ = "test_virtual_generic_method_cls"
    class OverloadingSubclass2(OverloadingSubclass):
        __namespace__ = "test_virtual_generic_method_cls"
    obj = OverloadingSubclass()
    assert obj.VirtMethod[int](5) == 5
    obj = OverloadingSubclass2()
    assert obj.VirtMethod[int](5) == 5

def test_implement_interface_and_class():
    class DualSubClass0(ISayHello1, SimpleClass):
        __namespace__ = "Test"
        def SayHello(self):
            return "hello"
    obj = DualSubClass0()
