# Copyright 2005-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import os

import portage


class WorldHandler:
    short_desc = "Fix problems in the world file"

    @staticmethod
    def name():
        return "world"

    def __init__(self):
        self.invalid = []
        self.masked = []
        self.missing = []
        self.not_installed = []
        self.okay = []
        from portage._sets import load_default_config

        setconfig = load_default_config(
            portage.settings, portage.db[portage.settings["EROOT"]]
        )
        self._sets = setconfig.getSets()

    def _check_world(self, onProgress):
        eroot = portage.settings["EROOT"]
        self.world_file = os.path.join(eroot, portage.const.WORLD_FILE)
        self.found = os.access(self.world_file, os.R_OK)
        vardb = portage.db[eroot]["vartree"].dbapi
        portdb = portage.db[eroot]["porttree"].dbapi

        from portage._sets import SETPREFIX
        from portage.package.ebuild.getmaskingstatus import getmaskingstatus

        sets = self._sets
        world_atoms = list(sets["selected"])
        maxval = len(world_atoms)
        if onProgress:
            onProgress(maxval, 0)
        for i, atom in enumerate(sorted(world_atoms, key=str)):
            if not isinstance(atom, portage.dep.Atom):
                if str(atom).startswith(SETPREFIX):
                    s = atom[len(SETPREFIX) :]
                    if s in sets:
                        self.okay.append(atom)
                    else:
                        self.not_installed.append(atom)
                else:
                    self.invalid.append(atom)
                if onProgress:
                    onProgress(maxval, i + 1)
                continue

            installed = vardb.match(atom)
            existing = portdb.xmatch("match-all", atom)
            if not existing:
                self.missing.append(atom)
            elif not installed:
                self.not_installed.append(atom)
            elif not portdb.xmatch("match-visible", atom):
                try:
                    reasons = {
                        r
                        for cpv in existing
                        for r in getmaskingstatus(cpv)
                        if "live" not in cpv._metadata.get("PROPERTIES", "").split()
                    }
                    reasons = f" [{', '.join(sorted(reasons))}]"
                except:
                    reasons = ""
                self.masked.append((atom, reasons))
            else:
                self.okay.append(atom)

            if onProgress:
                onProgress(maxval, i + 1)

    def check(self, **kwargs):
        onProgress = kwargs.get("onProgress", None)
        self._check_world(onProgress)
        errors = []
        if self.found:
            errors += [f"'{x}' has no visible ebuilds{r}" for x, r in self.masked]
            errors += [f"'{x}' has no available ebuilds" for x in self.missing]
            errors += [f"'{x}' is not a valid atom" for x in self.invalid]
            errors += [f"'{x}' is not installed" for x in self.not_installed]
        else:
            errors.append(self.world_file + " could not be opened for reading")
        if errors:
            return (False, errors)
        return (True, None)

    def fix(self, **kwargs):
        onProgress = kwargs.get("onProgress", None)
        world_set = self._sets["selected"]
        world_set.lock()
        try:
            world_set.load()  # maybe it's changed on disk
            before = set(world_set)
            self._check_world(onProgress)
            after = set(self.okay)
            errors = []
            if before != after:
                try:
                    world_set.replace(self.okay)
                except portage.exception.PortageException:
                    errors.append(f"{self.world_file} could not be opened for writing")
            if errors:
                return (False, errors)
            return (True, None)
        finally:
            world_set.unlock()
