"""Utilities for union (sum type) disambiguation."""
from collections import OrderedDict
from functools import reduce
from operator import or_

from typing import Mapping

from attr import fields


def create_uniq_field_dis_func(*cls):
    # type: (*Sequence[Type]) -> Callable
    """Given attr classes, generate a disambiguation function.

    The function is based on unique fields."""
    if len(cls) < 2:
        raise ValueError('At least two classes required.')
    cls_and_attrs = [(cl, set(at.name for at in fields(cl))) for cl in cls]
    if len([attrs for _, attrs in cls_and_attrs if len(attrs) == 0]) > 1:
        raise ValueError('At least two classes have no attributes.')
    # TODO: Deal with a single class having no required attrs.
    # For each class, attempt to generate a single unique required field.
    uniq_attrs_dict = OrderedDict()
    cls_and_attrs.sort(key=lambda c_a: -len(c_a[1]))

    fallback = None  # If none match, try this.

    for i, (cl, cl_reqs) in enumerate(cls_and_attrs):
        other_classes = cls_and_attrs[i+1:]
        if other_classes:
            other_reqs = reduce(or_, (c_a[1] for c_a in other_classes))
            uniq = cl_reqs - other_reqs
            if not uniq:
                m = '{} has no usable unique attributes.'.format(cl)
                raise ValueError(m)
            uniq_attrs_dict[next(iter(uniq))] = cl
        else:
            fallback = cl

    def dis_func(data):
        # type: (Mapping) -> Union
        if not isinstance(data, Mapping):
            raise ValueError('Only input mappings are supported.')
        for k, v in uniq_attrs_dict.items():
            if k in data:
                return v
        return fallback

    return dis_func
