"""Flexible matchers for various collection types in a fluent style."""
from types import GeneratorType

from h_matchers.exception import NoMatch
from h_matchers.matcher.collection import _mixin
from h_matchers.matcher.core import Matcher


class AnyCollection(
    _mixin.SizeMixin,
    _mixin.TypeMixin,
    _mixin.ItemMatcherMixin,
    _mixin.ContainsMixin,
    Matcher,
):
    """Matches any iterable with options for constraining contents and size."""

    def __init__(self):
        # Pass None as our function, as we will be in charge of our own type
        # checking
        super().__init__("dummy", self.assert_equal_to)

    def assert_equal_to(self, other):
        try:
            copy = list(other)
        except TypeError as err:
            raise AssertionError("Object is not iterable") from err

        # Execute checks roughly in complexity order
        for checker in [
            self._check_type,
            self._check_size,
            self._check_item_matcher,
            self._check_contains,
        ]:
            try:
                checker(copy, original=other)
            except NoMatch as err:
                raise AssertionError("No match could be found") from err

        return True

    def __str__(self):
        # This is some pretty gross code, but it makes test output so much
        # more readable
        parts = ["any"]

        parts.extend(self._describe_type())
        parts.extend(self._describe_size())
        parts.extend(self._describe_contains())
        parts.extend(self._describe_item_matcher())

        return f'* {" ".join(parts)} *'


class AnyDict(AnyCollection):
    """A matcher representing any dict."""

    _exact_type = dict


class AnySet(AnyCollection):
    """A matcher representing any set."""

    _exact_type = set


class AnyList(AnyCollection):
    """A matcher representing any list."""

    _exact_type = list


class AnyTuple(AnyCollection):
    """A matcher representing any tuple."""

    _exact_type = tuple


class AnyGenerator(AnyCollection):
    """A matcher representing any generator."""

    _exact_type = GeneratorType


class AnyMapping(AnyCollection):
    """A matcher representing any mapping."""

    def assert_equal_to(self, other):
        assert hasattr(other, "items"), "Mapping object needs items()"

        return super().assert_equal_to(other)
