from abc import ABCMeta, abstractmethod

from six import with_metaclass, iteritems

# Consistent error to be thrown in various cases regarding overriding
# `final` attributes.
_type_error = TypeError('Cannot override final attribute')


def bases_mro(bases):
    """
    Yield classes in the order that methods should be looked up from the
    base classes of an object.
    """
    for base in bases:
        for class_ in base.__mro__:
            yield class_


def is_final(name, mro):
    """
    Checks if `name` is a `final` object in the given `mro`.
    We need to check the mro because we need to directly go into the __dict__
    of the classes. Because `final` objects are descriptor, we need to grab
    them _BEFORE_ the `__call__` is invoked.
    """
    return any(isinstance(getattr(c, '__dict__', {}).get(name), final)
               for c in bases_mro(mro))


class FinalMeta(type):
    """A metaclass template for classes the want to prevent subclassess from
    overriding a some methods or attributes.
    """
    def __new__(mcls, name, bases, dict_):
        for k, v in iteritems(dict_):
            if is_final(k, bases):
                raise _type_error

        setattr_ = dict_.get('__setattr__')
        if setattr_ is None:
            # No `__setattr__` was explicitly defined, look up the super
            # class's. `bases[0]` will have a `__setattr__` because
            # `object` does so we don't need to worry about the mro.
            setattr_ = bases[0].__setattr__

        if not is_final('__setattr__', bases) \
           and not isinstance(setattr_, final):
            # implicitly make the `__setattr__` a `final` object so that
            # users cannot just avoid the descriptor protocol.
            dict_['__setattr__'] = final(setattr_)

        return super(FinalMeta, mcls).__new__(mcls, name, bases, dict_)

    def __setattr__(self, name, value):
        """This stops the `final` attributes from being reassigned on the
        class object.
        """
        if is_final(name, self.__mro__):
            raise _type_error

        super(FinalMeta, self).__setattr__(name, value)


class final(with_metaclass(ABCMeta)):
    """
    An attribute that cannot be overridden.
    This is like the final modifier in Java.

    Example usage:
    >>> from six import with_metaclass
    >>> class C(with_metaclass(FinalMeta, object)):
    ...    @final
    ...    def f(self):
    ...        return 'value'
    ...

    This constructs a class with final method `f`. This cannot be overridden
    on the class object or on any instance. You cannot override this by
    subclassing `C`; attempting to do so will raise a `TypeError` at class
    construction time.
    """
    def __new__(cls, attr):
        # Decide if this is a method wrapper or an attribute wrapper.
        # We are going to cache the `callable` check by creating a
        # method or attribute wrapper.
        if hasattr(attr, '__get__'):
            return object.__new__(finaldescriptor)
        else:
            return object.__new__(finalvalue)

    def __init__(self, attr):
        self._attr = attr

    def __set__(self, instance, value):
        """
        `final` objects cannot be reassigned. This is the most import concept
        about `final`s.

        Unlike a `property` object, this will raise a `TypeError` when you
        attempt to reassign it.
        """
        raise _type_error

    @abstractmethod
    def __get__(self, instance, owner):
        raise NotImplementedError('__get__')


class finalvalue(final):
    """
    A wrapper for a non-descriptor attribute.
    """
    def __get__(self, instance, owner):
        return self._attr


class finaldescriptor(final):
    """
    A final wrapper around a descriptor.
    """
    def __get__(self, instance, owner):
        return self._attr.__get__(instance, owner)
