# Copyright 1999-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2


import portage
from portage import os
from portage import digraph
from portage._sets.base import InternalPackageSet
from portage.dep import Atom

from _emerge.BlockerCache import BlockerCache
from _emerge.Package import Package
from _emerge.show_invalid_depstring_notice import show_invalid_depstring_notice


class BlockerDB:
    def __init__(self, fake_vartree):
        root_config = fake_vartree._root_config
        self._root_config = root_config
        self._vartree = root_config.trees["vartree"]
        self._portdb = root_config.trees["porttree"].dbapi

        self._dep_check_trees = None
        self._fake_vartree = fake_vartree
        self._dep_check_trees = {
            self._vartree.settings["EROOT"]: {
                "porttree": fake_vartree,
                "vartree": fake_vartree,
            }
        }

    def findInstalledBlockers(self, new_pkg):
        """
        Search for installed run-time blockers in the root where
        new_pkg is planned to be installed. This ignores build-time
        blockers, since new_pkg is assumed to be built already.
        """
        blocker_cache = BlockerCache(None, self._vartree.dbapi)
        dep_keys = Package._runtime_keys
        settings = self._vartree.settings
        stale_cache = set(blocker_cache)
        fake_vartree = self._fake_vartree
        dep_check_trees = self._dep_check_trees
        vardb = fake_vartree.dbapi
        installed_pkgs = list(vardb)

        for inst_pkg in installed_pkgs:
            stale_cache.discard(inst_pkg.cpv)
            cached_blockers = blocker_cache.get(inst_pkg.cpv)
            if (
                cached_blockers is not None
                and cached_blockers.counter != inst_pkg.counter
            ):
                cached_blockers = None
            if cached_blockers is not None:
                blocker_atoms = cached_blockers.atoms
            else:
                # Use aux_get() to trigger FakeVartree global
                # updates on *DEPEND when appropriate.
                depstr = " ".join(vardb.aux_get(inst_pkg.cpv, dep_keys))
                success, atoms = portage.dep_check(
                    depstr,
                    vardb,
                    settings,
                    myuse=inst_pkg.use.enabled,
                    trees=dep_check_trees,
                    myroot=inst_pkg.root,
                )
                if not success:
                    pkg_location = os.path.join(
                        inst_pkg.root, portage.VDB_PATH, inst_pkg.category, inst_pkg.pf
                    )
                    portage.writemsg(
                        f"!!! {pkg_location}/*DEPEND: {atoms}\n",
                        noiselevel=-1,
                    )
                    continue

                blocker_atoms = [atom for atom in atoms if atom.startswith("!")]
                blocker_atoms.sort()
                blocker_cache[inst_pkg.cpv] = blocker_cache.BlockerData(
                    inst_pkg.counter, blocker_atoms
                )
        for cpv in stale_cache:
            del blocker_cache[cpv]
        blocker_cache.flush()

        blocker_parents = digraph()
        blocker_atoms = []
        for pkg in installed_pkgs:
            for blocker_atom in blocker_cache[pkg.cpv].atoms:
                blocker_atom = blocker_atom.lstrip("!")
                blocker_atoms.append(blocker_atom)
                blocker_parents.add(blocker_atom, pkg)

        blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms)
        blocking_pkgs = set()
        for atom in blocker_atoms.iterAtomsForPackage(new_pkg):
            blocking_pkgs.update(blocker_parents.parent_nodes(atom))

        # Check for blockers in the other direction.
        depstr = " ".join(new_pkg._metadata[k] for k in dep_keys)
        success, atoms = portage.dep_check(
            depstr,
            vardb,
            settings,
            myuse=new_pkg.use.enabled,
            trees=dep_check_trees,
            myroot=new_pkg.root,
        )
        if not success:
            # We should never get this far with invalid deps.
            show_invalid_depstring_notice(new_pkg, atoms)
            assert False

        blocker_atoms = [atom.lstrip("!") for atom in atoms if atom[:1] == "!"]
        if blocker_atoms:
            blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms)
            for inst_pkg in installed_pkgs:
                try:
                    next(blocker_atoms.iterAtomsForPackage(inst_pkg))
                except (portage.exception.InvalidDependString, StopIteration):
                    continue
                blocking_pkgs.add(inst_pkg)

        return blocking_pkgs

    def discardBlocker(self, pkg):
        """Discard a package from the list of potential blockers.
        This will match any package(s) with identical cpv or cp:slot."""
        for cpv_match in self._fake_vartree.dbapi.match_pkgs(Atom(f"={pkg.cpv}")):
            if cpv_match.cp == pkg.cp:
                self._fake_vartree.cpv_discard(cpv_match)
        for slot_match in self._fake_vartree.dbapi.match_pkgs(pkg.slot_atom):
            if slot_match.cp == pkg.cp:
                self._fake_vartree.cpv_discard(slot_match)
