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

__all__ = ["getmaskingstatus"]

import portage
from portage import eapi_is_supported, _eapi_is_deprecated
from portage.exception import InvalidDependString
from portage.localization import _
from portage.package.ebuild.config import config
from portage.versions import _pkg_str


class _UnmaskHint:
    __slots__ = ("key", "value")

    def __init__(self, key, value):
        self.key = key
        self.value = value


class _MaskReason:
    __slots__ = ("category", "message", "unmask_hint")

    def __init__(self, category, message, unmask_hint=None):
        self.category = category
        self.message = message
        self.unmask_hint = unmask_hint


def getmaskingstatus(mycpv, settings=None, portdb=None, myrepo=None):
    if settings is None:
        settings = config(clone=portage.settings)
    if portdb is None:
        portdb = portage.portdb

    return [
        mreason.message
        for mreason in _getmaskingstatus(mycpv, settings, portdb, myrepo)
    ]


def _getmaskingstatus(mycpv, settings, portdb, myrepo=None):
    metadata = None
    installed = False
    if not isinstance(mycpv, str):
        # emerge passed in a Package instance
        pkg = mycpv
        mycpv = pkg.cpv
        metadata = pkg._metadata
        installed = pkg.installed

    if metadata is None:
        db_keys = list(portdb._aux_cache_keys)
        try:
            metadata = dict(zip(db_keys, portdb.aux_get(mycpv, db_keys, myrepo=myrepo)))
        except KeyError:
            if not portdb.cpv_exists(mycpv):
                raise
            return [_MaskReason("corruption", "corruption")]
        if "?" in metadata["LICENSE"]:
            settings.setcpv(mycpv, mydb=metadata)
            metadata["USE"] = settings["PORTAGE_USE"]
        else:
            metadata["USE"] = ""

    try:
        mycpv.slot
    except AttributeError:
        try:
            mycpv = _pkg_str(mycpv, metadata=metadata, settings=settings)
        except portage.exception.InvalidData:
            raise ValueError(_("invalid CPV: %s") % mycpv)

    rValue = []

    # package.mask checking
    if settings._getMaskAtom(mycpv, metadata):
        rValue.append(
            _MaskReason("package.mask", "package.mask", _UnmaskHint("p_mask", None))
        )

    # keywords checking
    eapi = metadata["EAPI"]
    mygroups = settings._getKeywords(mycpv, metadata)
    licenses = metadata["LICENSE"]
    properties = metadata["PROPERTIES"]
    restrict = metadata["RESTRICT"]
    if not eapi_is_supported(eapi):
        return [_MaskReason("EAPI", f"EAPI {eapi}")]
    if _eapi_is_deprecated(eapi) and not installed:
        return [_MaskReason("EAPI", f"EAPI {eapi}")]
    egroups = settings.configdict["backupenv"].get("ACCEPT_KEYWORDS", "").split()
    global_accept_keywords = settings.get("ACCEPT_KEYWORDS", "")
    pgroups = global_accept_keywords.split()
    myarch = settings["ARCH"]
    if pgroups and myarch not in pgroups:
        """For operating systems other than Linux, ARCH is not necessarily a
        valid keyword."""
        myarch = pgroups[0].lstrip("~")

    # NOTE: This logic is copied from KeywordsManager.getMissingKeywords().
    unmaskgroups = settings._keywords_manager.getPKeywords(
        mycpv, metadata["SLOT"], metadata["repository"], global_accept_keywords
    )
    pgroups.extend(unmaskgroups)
    if unmaskgroups or egroups:
        pgroups = settings._keywords_manager._getEgroups(egroups, pgroups)
    else:
        pgroups = set(pgroups)

    kmask = "missing"
    kmask_hint = None

    if "**" in pgroups:
        kmask = None
    else:
        for keyword in pgroups:
            if keyword in mygroups:
                kmask = None
                break

    if kmask:
        for gp in mygroups:
            if gp == "*":
                kmask = None
                break
            elif gp == "~*":
                for x in pgroups:
                    if x[:1] == "~":
                        kmask = None
                        break
                if kmask is None:
                    break
            elif gp == "-" + myarch and myarch in pgroups:
                kmask = "-" + myarch
                break
            elif gp == "~" + myarch and myarch in pgroups:
                kmask = "~" + myarch
                kmask_hint = _UnmaskHint("unstable keyword", kmask)
                break

    if kmask == "missing":
        kmask_hint = _UnmaskHint("unstable keyword", "**")

    try:
        missing_licenses = settings._getMissingLicenses(mycpv, metadata)
        if missing_licenses:
            allowed_tokens = {"||", "(", ")"}
            allowed_tokens.update(missing_licenses)
            license_split = licenses.split()
            license_split = [x for x in license_split if x in allowed_tokens]
            msg = license_split[:]
            msg.append("license(s)")
            rValue.append(
                _MaskReason(
                    "LICENSE",
                    " ".join(msg),
                    _UnmaskHint("license", set(missing_licenses)),
                )
            )
    except portage.exception.InvalidDependString as e:
        rValue.append(_MaskReason("invalid", "LICENSE: " + str(e)))

    try:
        missing_properties = settings._getMissingProperties(mycpv, metadata)
        if missing_properties:
            allowed_tokens = {"||", "(", ")"}
            allowed_tokens.update(missing_properties)
            properties_split = properties.split()
            properties_split = [x for x in properties_split if x in allowed_tokens]
            msg = properties_split[:]
            msg.append("properties")
            rValue.append(_MaskReason("PROPERTIES", " ".join(msg)))
    except portage.exception.InvalidDependString as e:
        rValue.append(_MaskReason("invalid", "PROPERTIES: " + str(e)))

    try:
        missing_restricts = settings._getMissingRestrict(mycpv, metadata)
        if missing_restricts:
            msg = list(missing_restricts)
            msg.append("in RESTRICT")
            rValue.append(_MaskReason("RESTRICT", " ".join(msg)))
    except InvalidDependString as e:
        rValue.append(_MaskReason("invalid", f"RESTRICT: {e}"))

    # Only show KEYWORDS masks for installed packages
    # if they're not masked for any other reason.
    if kmask and (not installed or not rValue):
        rValue.append(
            _MaskReason("KEYWORDS", kmask + " keyword", unmask_hint=kmask_hint)
        )

    return rValue
