# Copyright 2019-2020 Lawrence Livermore National Security, LLC and other
# Archspec Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""Aliases for microarchitecture features."""
from typing import Callable, Dict

from .schema import TARGETS_JSON, LazyDictionary

_FEATURE_ALIAS_PREDICATE: Dict[str, Callable] = {}


class FeatureAliasTest:
    """A test that must be passed for a feature alias to succeed.

    Args:
        rules (dict): dictionary of rules to be met. Each key must be a
            valid alias predicate
    """

    # pylint: disable=too-few-public-methods
    def __init__(self, rules):
        self.rules = rules
        self.predicates = []
        for name, args in rules.items():
            self.predicates.append(_FEATURE_ALIAS_PREDICATE[name](args))

    def __call__(self, microarchitecture):
        return all(feature_test(microarchitecture) for feature_test in self.predicates)


def _feature_aliases():
    """Returns the dictionary of all defined feature aliases."""
    json_data = TARGETS_JSON["feature_aliases"]
    aliases = {}
    for alias, rules in json_data.items():
        aliases[alias] = FeatureAliasTest(rules)
    return aliases


FEATURE_ALIASES = LazyDictionary(_feature_aliases)


def alias_predicate(func):
    """Decorator to register a predicate that can be used to evaluate
    feature aliases.
    """
    name = func.__name__

    # Check we didn't register anything else with the same name
    if name in _FEATURE_ALIAS_PREDICATE:
        msg = f'the alias predicate "{name}" already exists'
        raise KeyError(msg)

    _FEATURE_ALIAS_PREDICATE[name] = func

    return func


@alias_predicate
def reason(_):
    """This predicate returns always True and it's there to allow writing
    a documentation string in the JSON file to explain why an alias is needed.
    """
    return lambda x: True


@alias_predicate
def any_of(list_of_features):
    """Returns a predicate that is True if any of the feature in the
    list is in the microarchitecture being tested, False otherwise.
    """

    def _impl(microarchitecture):
        return any(x in microarchitecture for x in list_of_features)

    return _impl


@alias_predicate
def families(list_of_families):
    """Returns a predicate that is True if the architecture family of
    the microarchitecture being tested is in the list, False otherwise.
    """

    def _impl(microarchitecture):
        return str(microarchitecture.family) in list_of_families

    return _impl
