import abc
import importlib
import typing

from .. import config

from . import tests

# This module defines Policy for testing measured boot logs.
# This module also implements a registry of policies and a few trivial
# policies.

# RefState is a succinct description of what is expected to be found
# in a measured boot log.  A Policy maps one of these to the
# corresponding Test.
RefState = typing.Mapping[str, tests.Data]


# Policy maps RefState expressed in some convenient form into the
# corresponding Test to apply to the log.  The log is Python data
# corresponding to JSON, and is the result of parsing and enriching.
class Policy(metaclass=abc.ABCMeta):
    """Policy can compile RefState into a Test"""

    @abc.abstractmethod
    def get_relevant_pcrs(self) -> typing.FrozenSet[int]:
        """Reveal the set of relevant PCR indices"""
        raise NotImplementedError

    @abc.abstractmethod
    def refstate_to_test(self, refstate: RefState) -> tests.Test:
        """Convert the given RefState into a precise Test"""
        raise NotImplementedError

    def evaluate(self, refstate: RefState, eventlog: tests.Data) -> str:
        """Evaluate and return the reason for rejection or empty string for accept"""
        tester = self.refstate_to_test(refstate)
        return tester.why_not({}, eventlog)


class AcceptAll(Policy):
    """Policy that accepts all eventlogs"""

    def get_relevant_pcrs(self) -> typing.FrozenSet[int]:
        return set()

    def refstate_to_test(self, refstate: RefState) -> tests.Test:
        return tests.AcceptAll()


def _mkreg() -> typing.Mapping[str, Policy]:
    return dict()


_registry = _mkreg()


def register(name: str, policy: Policy):
    """Remember the given policy under the given name"""
    _registry[name] = policy


register("accept-all", AcceptAll())


def get_policy_names() -> typing.Tuple[str, ...]:
    """Return the list of policy names"""
    return list(_registry.keys())


def get_policy(name: str) -> Policy:
    """Returns the Policy with the given name, None if there is none"""
    return _registry.get(name)


def refstate_to_test(policy_name: str, refstate: RefState) -> tests.Test:
    """Compiles the given RefState into the Test prescribed by the named policy"""
    policy = get_policy(policy_name)
    if policy is None:
        raise Exception(f'there is no policy named {policy_name!a}')
    return policy.refstate_to_test(refstate)


def evaluate(policy_name: str, refstate: RefState, eventlog: tests.Data) -> str:
    """Evaluate the given eventlog using given refstate and policy

    Returns either:
    (a) an empty string to signal a good result or
    (b) a non-empty string identifying something wrong.
    """
    tester = refstate_to_test(policy_name, refstate)
    return tester.why_not({}, eventlog)


imports = config.MEASUREDBOOT_IMPORTS
print(f'importing {imports!r}, __package__={__package__!r}')
for imp in imports:
    if imp:
        importlib.import_module(imp, __package__)
