import six

registry = {}


class ArbitraryMeta(type):
    def __init__(cls, name, bases, attrs):
        super(ArbitraryMeta, cls).__init__(name, bases, attrs)

    def __new__(meta, name, bases, attrs):
        cls = super(ArbitraryMeta, meta).__new__(meta, name, bases, attrs)

        b = next((b for b in bases if b.__name__ == 'Arbitrary'), None)
        if b:
            cls.__call__ = lambda self, *args, **kwargs: cls(*args, **kwargs)
            inst = cls()

            if getattr(b, '_type', None):
                registry[b._type] = inst
                del b._type

            return inst
        else:
            return cls

    def __call__(cls, *args, **kwargs):
        onearg = args[0] if len(args) == 1 and not kwargs else None
        if cls.__name__ == 'Arbitrary' and isinstance(onearg, type):
            cls._type = onearg
            return cls

        return super(ArbitraryMeta, cls).__call__(*args, **kwargs)


class Arbitrary(six.with_metaclass(ArbitraryMeta),
                six.Iterator):
    def __next__(self):
        """Method to be overriden.

        Get next arbitrary value."""
        raise StopIteration

    def get_edgecases(self):
        """Method to be overriden.

        Get (or generate) a finite (usually small) amount of edgecases."""
        return []

    def could_generate(self, x):
        """Method to be overriden.

        Check if x could've been generated by self."""
        raise NotImplementedError

    def shrink(self, x):
        """Method to be overriden.

        Get possible shrinkings of x.
        You can assume here that self.could_generate(x) == True."""
        return []

    @staticmethod
    def get_for(type):
        """Get default Arbitrary for the specified type."""
        return registry[type]
