# -*- coding: utf-8 -*-
import inspect
from ..operator import Operator


class RaisesOperator(Operator):
    """
    Asserts if a given function raises an exception.

    Example::

        def fn():
            raise ValueError('error')

        # Should style
        fn | should.raise_error()
        fn | should.raise_error(ValueError)
        fn | should.raise_error(AttributeError, ValueError)

        # Should style - negation form
        fn | should.do_not.raise_error()
        fn | should.do_not.raise_error(ValueError)
        fn | should.do_not.raise_error(AttributeError, ValueError)

        # Expect style
        fn | expect.to.raise_error()
        fn | expect.to.raise_error(ValueError)
        fn | expect.to.raise_error(AttributeError, ValueError)

        # Expect style - negation form
        fn | expect.to_not.raise_error()
        fn | expect.to_not.raise_error(ValueError)
        fn | expect.to_not.raise_error(AttributeError, ValueError)
    """

    # Is the operator a keyword
    kind = Operator.Type.MATCHER

    # Operator keywords
    operators = ('raises', 'raise_error', 'raise_errors')

    # Operator chain aliases
    aliases = ('to', 'that', 'are', 'instance', 'of')

    # Expected template message
    expected_message = Operator.Dsl.Message(
        'a callable object that raises the exception(s) "{value}"',
        'a callable object that do not raise the exception(s) "{value}"',
    )

    # Subject template message
    subject_message = Operator.Dsl.Message(
        'an object of type "{type}" with reference "{value}"',
    )

    def match(self, fn, *errors):
        if not any([inspect.isfunction(fn) or inspect.ismethod(fn)]):
            return False, ['subject must be a function or method']

        try:
            fn()
        except Exception as err:
            self.value = err

            # If no errors present, just raise the exception
            if not errors:
                return True, []

            # Otherwise match errors
            return isinstance(err, *errors), ['invalid raised exception']
        else:
            return False, ['did not raise any exception']
