##############################################################################
#
# Copyright (c) 2014 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Resolution ordering utility tests"""
import unittest

# pylint:disable=blacklisted-name,protected-access,attribute-defined-outside-init

class Test__mergeOrderings(unittest.TestCase):

    def _callFUT(self, orderings):
        from zope.interface.ro import _legacy_mergeOrderings
        return _legacy_mergeOrderings(orderings)

    def test_empty(self):
        self.assertEqual(self._callFUT([]), [])

    def test_single(self):
        self.assertEqual(self._callFUT(['a', 'b', 'c']), ['a', 'b', 'c'])

    def test_w_duplicates(self):
        self.assertEqual(self._callFUT([['a'], ['b', 'a']]), ['b', 'a'])

    def test_suffix_across_multiple_duplicates(self):
        O1 = ['x', 'y', 'z']
        O2 = ['q', 'z']
        O3 = [1, 3, 5]
        O4 = ['z']
        self.assertEqual(self._callFUT([O1, O2, O3, O4]),
                         ['x', 'y', 'q', 1, 3, 5, 'z'])


class Test__flatten(unittest.TestCase):

    def _callFUT(self, ob):
        from zope.interface.ro import _legacy_flatten
        return _legacy_flatten(ob)

    def test_w_empty_bases(self):
        class Foo(object):
            pass
        foo = Foo()
        foo.__bases__ = ()
        self.assertEqual(self._callFUT(foo), [foo])

    def test_w_single_base(self):
        class Foo(object):
            pass
        self.assertEqual(self._callFUT(Foo), [Foo, object])

    def test_w_bases(self):
        class Foo(object):
            pass
        class Bar(Foo):
            pass
        self.assertEqual(self._callFUT(Bar), [Bar, Foo, object])

    def test_w_diamond(self):
        class Foo(object):
            pass
        class Bar(Foo):
            pass
        class Baz(Foo):
            pass
        class Qux(Bar, Baz):
            pass
        self.assertEqual(self._callFUT(Qux),
                         [Qux, Bar, Foo, object, Baz, Foo, object])


class Test_ro(unittest.TestCase):
    maxDiff = None
    def _callFUT(self, ob, **kwargs):
        from zope.interface.ro import _legacy_ro
        return _legacy_ro(ob, **kwargs)

    def test_w_empty_bases(self):
        class Foo(object):
            pass
        foo = Foo()
        foo.__bases__ = ()
        self.assertEqual(self._callFUT(foo), [foo])

    def test_w_single_base(self):
        class Foo(object):
            pass
        self.assertEqual(self._callFUT(Foo), [Foo, object])

    def test_w_bases(self):
        class Foo(object):
            pass
        class Bar(Foo):
            pass
        self.assertEqual(self._callFUT(Bar), [Bar, Foo, object])

    def test_w_diamond(self):
        class Foo(object):
            pass
        class Bar(Foo):
            pass
        class Baz(Foo):
            pass
        class Qux(Bar, Baz):
            pass
        self.assertEqual(self._callFUT(Qux),
                         [Qux, Bar, Baz, Foo, object])

    def _make_IOErr(self):
        # This can't be done in the standard C3 ordering.
        class Foo(object):
            def __init__(self, name, *bases):
                self.__name__ = name
                self.__bases__ = bases
            def __repr__(self): # pragma: no cover
                return self.__name__

        # Mimic what classImplements(IOError, IIOError)
        # does.
        IEx = Foo('IEx')
        IStdErr = Foo('IStdErr', IEx)
        IEnvErr = Foo('IEnvErr', IStdErr)
        IIOErr = Foo('IIOErr', IEnvErr)
        IOSErr = Foo('IOSErr', IEnvErr)

        IOErr = Foo('IOErr', IEnvErr, IIOErr, IOSErr)
        return IOErr, [IOErr, IIOErr, IOSErr, IEnvErr, IStdErr, IEx]

    def test_non_orderable(self):
        IOErr, bases = self._make_IOErr()

        self.assertEqual(self._callFUT(IOErr), bases)

    def test_mixed_inheritance_and_implementation(self):
        # https://github.com/zopefoundation/zope.interface/issues/8
        # This test should fail, but doesn't, as described in that issue.
        # pylint:disable=inherit-non-class
        from zope.interface import implementer
        from zope.interface import Interface
        from zope.interface import providedBy
        from zope.interface import implementedBy

        class IFoo(Interface):
            pass

        @implementer(IFoo)
        class ImplementsFoo(object):
            pass

        class ExtendsFoo(ImplementsFoo):
            pass

        class ImplementsNothing(object):
            pass

        class ExtendsFooImplementsNothing(ExtendsFoo, ImplementsNothing):
            pass

        self.assertEqual(
            self._callFUT(providedBy(ExtendsFooImplementsNothing())),
            [implementedBy(ExtendsFooImplementsNothing),
             implementedBy(ExtendsFoo),
             implementedBy(ImplementsFoo),
             IFoo,
             Interface,
             implementedBy(ImplementsNothing),
             implementedBy(object)])


class C3Setting(object):

    def __init__(self, setting, value):
        self._setting = setting
        self._value = value

    def __enter__(self):
        from zope.interface import ro
        setattr(ro.C3, self._setting.__name__, self._value)

    def __exit__(self, t, v, tb):
        from zope.interface import ro
        setattr(ro.C3, self._setting.__name__, self._setting)

class TestC3(unittest.TestCase):
    def _makeOne(self, C, strict=False, base_mros=None):
        from zope.interface.ro import C3
        return C3.resolver(C, strict, base_mros)

    def test_base_mros_given(self):
        c3 = self._makeOne(type(self), base_mros={unittest.TestCase: unittest.TestCase.__mro__})
        memo = c3.memo
        self.assertIn(unittest.TestCase, memo)
        # We used the StaticMRO class
        self.assertIsNone(memo[unittest.TestCase].had_inconsistency)

    def test_one_base_optimization(self):
        c3 = self._makeOne(type(self))
        # Even though we didn't call .mro() yet, the MRO has been
        # computed.
        self.assertIsNotNone(c3._C3__mro) # pylint:disable=no-member
        c3._merge = None
        self.assertEqual(c3.mro(), list(type(self).__mro__))


class Test_ROComparison(unittest.TestCase):

    class MockC3(object):
        direct_inconsistency = False
        bases_had_inconsistency = False

    def _makeOne(self, c3=None, c3_ro=(), legacy_ro=()):
        from zope.interface.ro import _ROComparison
        return _ROComparison(c3 or self.MockC3(), c3_ro, legacy_ro)

    def test_inconsistent_label(self):
        comp = self._makeOne()
        self.assertEqual('no', comp._inconsistent_label)

        comp.c3.direct_inconsistency = True
        self.assertEqual("direct", comp._inconsistent_label)

        comp.c3.bases_had_inconsistency = True
        self.assertEqual("direct+bases", comp._inconsistent_label)

        comp.c3.direct_inconsistency = False
        self.assertEqual('bases', comp._inconsistent_label)