"""
Tests for `attr._make`.
"""

from __future__ import absolute_import, division, print_function

import pytest

from . import simple_attr
from attr import _config
from attr._compat import PY3
from attr._make import (
    Attribute,
    NOTHING,
    _CountingAttr,
    _transform_attrs,
    attr,
    attributes,
    fields,
    make_class,
    validate,
)


class TestCountingAttr(object):
    """
    Tests for `attr`.
    """
    def test_returns_Attr(self):
        """
        Returns an instance of _CountingAttr.
        """
        a = attr()
        assert isinstance(a, _CountingAttr)


def make_tc():
    class TransformC(object):
        z = attr()
        y = attr()
        x = attr()
        a = 42
    return TransformC


class TestTransformAttrs(object):
    """
    Tests for `_transform_attrs`.
    """
    def test_normal(self):
        """
        Transforms every `_CountingAttr` and leaves others (a) be.
        """
        C = make_tc()
        _transform_attrs(C, None)
        assert ["z", "y", "x"] == [a.name for a in C.__attrs_attrs__]

    def test_empty(self):
        """
        No attributes works as expected.
        """
        @attributes
        class C(object):
            pass

        _transform_attrs(C, None)

        assert () == C.__attrs_attrs__

    @pytest.mark.parametrize("attribute", [
        "z",
        "y",
        "x",
    ])
    def test_transforms_to_attribute(self, attribute):
        """
        All `_CountingAttr`s are transformed into `Attribute`s.
        """
        C = make_tc()
        _transform_attrs(C, None)

        assert isinstance(getattr(C, attribute), Attribute)

    def test_conflicting_defaults(self):
        """
        Raises `ValueError` if attributes with defaults are followed by
        mandatory attributes.
        """
        class C(object):
            x = attr(default=None)
            y = attr()

        with pytest.raises(ValueError) as e:
            _transform_attrs(C, None)
        assert (
            "No mandatory attributes allowed after an attribute with a "
            "default value or factory.  Attribute in question: Attribute"
            "(name='y', default=NOTHING, validator=None, repr=True, "
            "cmp=True, hash=True, init=True)",
        ) == e.value.args

    def test_these(self):
        """
        If these is passed, use it and ignore body.
        """
        class C(object):
            y = attr()

        _transform_attrs(C, {"x": attr()})
        assert (
            simple_attr("x"),
        ) == C.__attrs_attrs__
        assert isinstance(C.y, _CountingAttr)

    def test_recurse(self):
        """
        Collect attributes from all sub-classes.
        """
        class A(object):
            pass

        class C(A):
            x = attr()

        _transform_attrs(C, None)

        class D(C):
            y = attr()

        _transform_attrs(D, None)

        assert (
            simple_attr("x"),
            simple_attr("y"),
        ) == D.__attrs_attrs__


class TestAttributes(object):
    """
    Tests for the `attributes` class decorator.
    """
    @pytest.mark.skipif(PY3, reason="No old-style classes in Py3")
    def test_catches_old_style(self):
        """
        Raises TypeError on old-style classes.
        """
        with pytest.raises(TypeError) as e:
            @attributes
            class C:
                pass
        assert ("attrs only works with new-style classes.",) == e.value.args

    def test_sets_attrs(self):
        """
        Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s.
        """
        @attributes
        class C(object):
            x = attr()
        assert "x" == C.__attrs_attrs__[0].name
        assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__)

    def test_empty(self):
        """
        No attributes, no problems.
        """
        @attributes
        class C3(object):
            pass
        assert "C3()" == repr(C3())
        assert C3() == C3()

    @pytest.mark.parametrize("method_name", [
        "__repr__",
        "__eq__",
        "__hash__",
        "__init__",
    ])
    def test_adds_all_by_default(self, method_name):
        """
        If no further arguments are supplied, all add_XXX functions are
        applied.
        """
        # Set the method name to a sentinel and check whether it has been
        # overwritten afterwards.
        sentinel = object()

        class C1(object):
            x = attr()

        setattr(C1, method_name, sentinel)

        C1 = attributes(C1)

        class C2(object):
            x = attr()

        setattr(C2, method_name, sentinel)

        C2 = attributes(C2)

        assert sentinel != getattr(C1, method_name)
        assert sentinel != getattr(C2, method_name)

    @pytest.mark.parametrize("arg_name, method_name", [
        ("repr", "__repr__"),
        ("cmp", "__eq__"),
        ("hash", "__hash__"),
        ("init", "__init__"),
    ])
    def test_respects_add_arguments(self, arg_name, method_name):
        """
        If a certain `add_XXX` is `True`, XXX is not added to the class.
        """
        # Set the method name to a sentinel and check whether it has been
        # overwritten afterwards.
        sentinel = object()

        am_args = {
            "repr": True,
            "cmp": True,
            "hash": True,
            "init": True
        }
        am_args[arg_name] = False

        class C(object):
            x = attr()

        setattr(C, method_name, sentinel)

        C = attributes(**am_args)(C)

        assert sentinel == getattr(C, method_name)

    @pytest.mark.skipif(not PY3, reason="__qualname__ is PY3-only.")
    def test_repr_qualname(self):
        """
        On Python 3, the name in repr is the __qualname__.
        """
        @attributes
        class C(object):
            @attributes
            class D(object):
                pass

        assert "C.D()" == repr(C.D())
        assert "GC.D()" == repr(GC.D())

    def test_repr_fake_qualname(self):
        """
        Setting repr_ns overrides a potentially guessed namespace.
        """
        @attributes
        class C(object):
            @attributes(repr_ns="C")
            class D(object):
                pass
        assert "C.D()" == repr(C.D())


@attributes
class GC(object):
    @attributes
    class D(object):
        pass