import sys
import pytest
from amoco.signals import Signal
from amoco.signals import (reference, REF_METH_BOUND, REF_FUNC_REGULAR,
                           REF_FUNC_UNBOUND, REF_OBJ_CALLABLE)

sig1 = Signal("#1")
sig2 = Signal("#2")
sig3 = Signal("#3")


class SigRaised(Exception):
    pass


def MyFunction1(x, y, z=0):
    def MyFunction2(x, y, z):
        return x + y + z

    return MyFunction2(x, y, z)


def Myaction1(sig, ref, args):
    assert isinstance(sig, Signal)
    raise SigRaised


def Myaction2(sig, ref, args):
    pass
    # pdb.set_trace()


def Myaction3(sig, ref, args):
    sig3.emit()


def Myaction4(sig, ref, args):
    print(sig)
    print(ref)
    print(args)


class MyClassA(object):
    @staticmethod
    def smeth(x, y, z=1):
        return x + y + z

    @classmethod
    def cmeth(cls, x, y, z=2):
        return x + y + z

    def meth(self, x, y, z=3, q=4):
        return x + y + z + q

    def __call__(self, x, y, z=4, q=5):
        return x + y + z + q

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, v):
        self._x = v


class MyClassB(MyClassA):
    @staticmethod
    def smethb(x, y, z=1):
        return x + y + z

    @classmethod
    def cmethb(cls, x, y, z=2):
        return x + y + z

    def methb(self, x, y, z=3, q=4):
        return x + y + z + q

    def __call__(self, x, y, z=4, q=5):
        return x + y + z + q


@pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="pypy")
def test_references():
    a = MyClassA()
    b = MyClassB()
    r = reference(a.meth)
    rf = reference(MyFunction1)
    rA = reference(MyClassA.meth)
    rsA = reference(a.smeth)
    rsB = reference(b.smeth)
    rcA = reference(a.cmeth)
    rcB = reference(MyClassB.cmeth)
    roa = reference(a)
    rob = reference(b)
    assert r.type == REF_METH_BOUND
    assert rf.type == REF_FUNC_REGULAR
    assert rA.type == REF_FUNC_UNBOUND
    assert rsA.type == REF_FUNC_UNBOUND and rsA.is_static
    assert rsB.type == REF_FUNC_UNBOUND and rsB.is_static
    assert rsB.ctx is rsA.ctx
    assert rsB.ref is rsA.ref
    assert rsB == rsA
    assert rcA.type == REF_METH_BOUND
    assert rcB.type == REF_METH_BOUND
    assert roa.type == REF_OBJ_CALLABLE
    assert rob.type == REF_OBJ_CALLABLE
    del a
    assert r.ctx() is None


def test_signal_0():
    assert Signal("#1") == sig1
    assert Signal("#1") is sig1


def test_signal_1():
    a = MyClassA()
    r = reference(a.meth)
    sig1.receiver(Myaction1)
    sig1.patch(r)
    assert MyClassA().meth(1, 2) == 10
    try:
        assert a.meth(1, 2) == 10
    except SigRaised:
        sig1.unpatch(a.meth)
    else:
        assert False
    assert a.meth(1, 2) == 10
    sig1.recv.remove(Myaction1)


def test_signal_2():
    sig1.sender(MyClassB.methb)
    sig1.receiver(Myaction1)
    b = MyClassB()
    try:
        assert MyClassB().methb(1, 2) == 10
    except SigRaised:
        pass
    else:
        assert False
    try:
        assert b.methb(1, 2) == 10
    except SigRaised:
        pass
    else:
        assert False
    sig1.unpatch(MyClassB.methb)
    sig1.recv.remove(Myaction1)
    assert b.methb(1, 2) == 10


def test_signal_3():
    b = MyClassB()
    sig1.sender(MyClassA.smeth)
    sig1.sender(b.meth)
    sig1.receiver(Myaction3)
    sig3.receiver(Myaction1)
    try:
        assert MyClassA().smeth(1, 1) == 3
    except SigRaised:
        pass
    else:
        assert False
    try:
        assert b.meth(1, 2) == 10
    except SigRaised:
        pass
    else:
        assert False
    sig1.unpatch(MyClassA.smeth)
    sig1.recv.remove(Myaction3)
    sig3.recv.remove(Myaction1)
    assert MyClassA().smeth(1, 1) == 3


def test_signal_4():
    b = MyClassB()
    sig1.sender(MyClassA.cmeth)
    sig1.sender(b.meth)
    sig1.receiver(Myaction3)
    sig3.receiver(Myaction1)
    try:
        assert MyClassA().cmeth(1, 1) == 4
    except SigRaised:
        pass
    else:
        assert False
    try:
        assert b.meth(1, 2) == 10
    except SigRaised:
        pass
    else:
        assert False
    sig1.unpatch(MyClassA.cmeth)
    sig1.recv.remove(Myaction3)
    sig3.recv.remove(Myaction1)
    assert MyClassA().cmeth(1, 1) == 4


def test_signal_5():
    sig2.receiver(Myaction2)
    sig2.emit()
    sig2.recv.remove(Myaction2)
