"""Utilities for algebraic rules"""

import inspect
from collections.__init__ import OrderedDict


__private__ = ['check_rules_dict']


def check_rules_dict(rules):
    """Verify the `rules` that classes may use for the `_rules` or
    `_binary_rules` class attribute.

    Specifically, `rules` must be a
    :class:`~collections.OrderedDict`-compatible object
    (list of key-value tuples, :class:`dict`,
    :class:`~collections.OrderedDict`)
    that maps a rule name (:class:`str`) to a rule. Each rule consists of a
    :class:`~qalgebra.pattern_matching.Pattern` and a replaceent callable.
    The Pattern must be set up to match a
    :class:`~qalgebra.pattern_matching.ProtoExpr`. That is,
    the Pattern should be constructed through the
    :func:`~qalgebra.pattern_matching.pattern_head` routine.

    Raises:
        TypeError: If `rules` is not compatible with
            :class:`~collections.OrderedDict`, the
            keys in `rules` are not strings, or rule is not a tuple of
            (:class:`~qalgebra.pattern_matching.Pattern`, `callable`)
        ValueError: If the `head`-attribute of each Pattern is not an instance
            of :class:`~qalgebra.pattern_matching.ProtoExpr`, or if there
            are duplicate keys in `rules`

    Returns:
        :class:`~collections.OrderedDict` of rules
    """
    from qalgebra.pattern_matching import Pattern, ProtoExpr

    if hasattr(rules, 'items'):
        items = rules.items()  # `rules` is already a dict / OrderedDict
    else:
        items = rules  # `rules` is a list of (key, value) tuples
    keys = set()
    for key_rule in items:
        try:
            key, rule = key_rule
        except ValueError:
            raise TypeError("rules does not contain (key, rule) tuples")
        if not isinstance(key, str):
            raise TypeError("Key '%s' is not a string" % key)
        if key in keys:
            raise ValueError("Duplicate key '%s'" % key)
        else:
            keys.add(key)
        try:
            pat, replacement = rule
        except TypeError:
            raise TypeError(
                "Rule in '%s' is not a (pattern, replacement) tuple" % key
            )
        if not isinstance(pat, Pattern):
            raise TypeError("Pattern in '%s' is not a Pattern instance" % key)
        if pat.head is not ProtoExpr:
            raise ValueError(
                "Pattern in '%s' does not match a ProtoExpr" % key
            )
        if not callable(replacement):
            raise ValueError("replacement in '%s' is not callable" % key)
        else:
            arg_names = inspect.signature(replacement).parameters.keys()
            if not arg_names == pat.wc_names:
                raise ValueError(
                    "arguments (%s) of replacement function differ from the "
                    "wildcard names (%s) in pattern"
                    % (
                        ", ".join(sorted(arg_names)),
                        ", ".join(sorted(pat.wc_names)),
                    )
                )
    return OrderedDict(rules)
