class Signing:

    def __init__(self):
        self._flavour_to_arch = {}
        self._package_to_flavour_to_arch = {}
        self._arch_flavour_data = {}

    def add(self, arch, stype, binary, flavours, options):
        for flavour in flavours:
            self._arch_flavour_data.setdefault((arch, flavour), set()).add((stype, binary))
            self._flavour_to_arch.setdefault(flavour, set()).add(arch)
            # cvm/uc/stubble/image are exclusive options; only emit one
            if "cvm" in options:
                pkg_type = "cvm"
            elif "uc" in options:
                pkg_type = "uc"
                self._arch_flavour_data.setdefault((arch, flavour), set()).add(("noop", "manifest-initramfs.yaml"))
            elif "fallback" in options:
                pkg_type = "fallback"
            else:
                pkg_type = "image"
            self._package_to_flavour_to_arch.setdefault(pkg_type, {}).setdefault(flavour, set()).add(arch)
            # all other options are supplementary to the image
            if "extra" in options:
                # cvm & uc sometimes need modules-extra
                self._package_to_flavour_to_arch.setdefault("extra", {}).setdefault(flavour, set()).add(arch)
            if "di" in options:
                self._package_to_flavour_to_arch.setdefault("di", {}).setdefault(flavour, set()).add(arch)
            if "hmac" in options:
                self._package_to_flavour_to_arch.setdefault("hmac", {}).setdefault(flavour, set()).add(arch)
            # record special image types -- typically to trigger additional dependencies.
            if binary == "kernel.efi":
                self._package_to_flavour_to_arch.setdefault("uci", {}).setdefault(flavour, set()).add(arch)
            elif binary == "stubble.efi":
                self._package_to_flavour_to_arch.setdefault("stubble", {}).setdefault(flavour, set()).add(arch)

    def flavour_option_archs(self, flavour, option):
        return self._package_to_flavour_to_arch.get("stubble", {}).get(flavour, set())

    @property
    def flavour_archs(self):
        for flavour, archs in sorted(self._flavour_to_arch.items()):
            yield flavour, sorted(list(archs))

    def package_flavour_archs(self, package):
        for flavour, archs in sorted(self._package_to_flavour_to_arch.get(package, {}).items()):
            yield flavour, sorted(list(archs))

    @property
    def arch_flavour_data(self):
        # allow for more than one binary to be signed by an arch+flavour pair
        # maintain backwards compatible API
        for (arch, flavour), stypebins in sorted(self._arch_flavour_data.items()):
            for (stype, binary) in sorted(stypebins):
                yield (arch, flavour), (stype, binary)

    def option_archs(self, option):
        option_archs = set()
        for _, archs in self.package_flavour_archs(option):
            option_archs.update(archs)
        return option_archs

    @classmethod
    def load(cls, config):
        signing = Signing()
        with open(config) as cfd:
            for line in cfd:
                cmd, *args = line.strip().split()
                if cmd == "sign":
                    arch, stype, binary, *flavours = args
                    options = []
                    while flavours[-1].startswith("--"):
                        options.append(flavours.pop()[2:])
                    signing.add(arch, stype, binary, flavours, options)
        return signing
