try:
    from enum import Enum, EnumMeta
except ImportError:
    # We are on Python 2.7 and enum34 package is not installed.
    class Enum(object):
        pass

    class EnumMeta(object):
        pass


from .exceptions import FailureProtocolError


def make_protocol(failures):
    if isinstance(failures, EnumMeta):
        protocol_class = EnumProtocol
    elif failures is not None:
        protocol_class = CollectionProtocol
    else:
        protocol_class = NullProtocol
    return protocol_class(failures)


class Protocol(object):
    def check_return_statement(self, obj, method, reason):
        if not reason:
            message = null_reason_template.format(
                available=self.available,
                cls=obj.__class__.__name__,
                method=method.__name__,
            )
            raise FailureProtocolError(message)
        if not self.check_reason(reason):
            message = wrong_reason_template.format(
                reason=reason,
                available=self.available,
                cls=obj.__class__.__name__,
                method=method.__name__,
            )
            raise FailureProtocolError(message)

    def check_reason(self, reason):

        raise NotImplementedError

    def summarize(self, cls_name, method_name, reason):
        # TODO: Deny to use `failed_because(None)`.
        if not self.check_reason(reason):
            message = wrong_summary_template.format(
                reason=reason,
                available=self.available,
                cls=cls_name,
                method=method_name,
            )
            raise FailureProtocolError(message)

    def cast_reason(self, reason):

        raise NotImplementedError

    def compare(self, story, cls_name, method_name):
        if not self.compare_other(story.protocol):
            message = mismatch_template.format(
                cls=cls_name,
                method=method_name,
                available=self.available,
                other_cls=story.cls_name,
                other_method=story.name,
                other_available=story.protocol.available,
            )
            raise FailureProtocolError(message)

    def compare_other(self, other):

        raise NotImplementedError


class NullProtocol(Protocol):
    def __init__(self, failures):
        self.failures = None
        self.available = "None"

    def check_return_statement(self, obj, method, reason):
        if reason:
            message = null_protocol_template.format(
                reason=reason, cls=obj.__class__.__name__, method=method.__name__
            )
            raise FailureProtocolError(message)

    def summarize(self, cls_name, method_name, reason):
        message = null_summary_template.format(cls=cls_name, method=method_name)
        raise FailureProtocolError(message)

    def cast_reason(self, reason):
        return None

    def compare_other(self, other):
        return other.failures is None


class CollectionProtocol(Protocol):
    def __init__(self, failures):
        self.failures = failures
        self.available = ", ".join(map(repr, failures))

    def check_reason(self, reason):
        return reason in self.failures

    def cast_reason(self, reason):
        return reason

    def compare_other(self, other):
        return other.failures is not None and set(self.failures) >= set(other.failures)


class EnumProtocol(Protocol):
    def __init__(self, failures):
        self.failures = failures
        self.available = ", ".join(map(repr, failures.__members__.values()))

    def check_reason(self, reason):
        return isinstance(reason, Enum) and reason.name in self.failures.__members__

    def cast_reason(self, reason):
        return self.failures.__members__[reason.name]

    def compare_other(self, other):
        return isinstance(other.failures, EnumMeta) and set(
            self.failures.__members__
        ) >= set(other.failures.__members__)


wrong_reason_template = """
Failure({reason!r}) failure reason is not allowed by current protocol.

Available failures are: {available}

Function returned value: {cls}.{method}
""".strip()


null_reason_template = """
Failure() can not be used in a story with failure protocol.

Available failures are: {available}

Function returned value: {cls}.{method}

Use one of them as Failure() argument.
""".strip()


null_protocol_template = """
Failure({reason!r}) can not be used in a story without failure protocol.

Function returned value: {cls}.{method}

Use 'failures' story method to define failure protocol.
""".strip()


wrong_summary_template = """
'failed_because' method got argument mismatching failure protocol: {reason!r}

Available failures are: {available}

Story returned result: {cls}.{method}
""".strip()


null_summary_template = """
'failed_because' method can not be used with story defined without failure protocol.

Story returned result: {cls}.{method}

Use 'failures' story method to define failure protocol.
""".strip()


mismatch_template = """
Story and substory failure protocol mismatch.

Story: {cls}.{method}

Available failures are: {available}

Substory: {other_cls}.{other_method}

Available failures are: {other_available}
""".strip()
