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


from portage import (
    _encodings,
    _unicode_encode,
    os,
)
from portage.dep.soname.parse import parse_soname_deps
from portage.util._dyn_libs.NeededEntry import NeededEntry


def _get_all_provides(vardb):
    """
    Get all of the sonames provided by all of the installed packages.
    This does not bother to acquire a lock, since its pretty safe to
    assume that any packages merged or unmerged while this function
    is running must be irrelevant.

    @param vardb: an installed package database
    @type vardb: vardbapi
    @rtype: frozenset
    @return: a frozenset od SonameAtom instances provided by all
            installed packages
    """

    all_provides = []

    for cpv in vardb.cpv_all():
        try:
            (provides,) = vardb.aux_get(cpv, ["PROVIDES"])
        except KeyError:
            # Since we don't hold a lock, assume this is due to a
            # concurrent unmerge, and PROVIDES from the unmerged package
            # are most likely negligible due to topologically sorted
            # merge order. Also, note that it's possible for aux_get
            # to succeed and return empty PROVIDES metadata if the file
            # disappears (due to unmerge) before it can be read.
            pass
        else:
            if provides:
                all_provides.extend(parse_soname_deps(provides))

    return frozenset(all_provides)


def _get_unresolved_soname_deps(metadata_dir, all_provides):
    """
    Get files with unresolved soname dependencies.

    @param metadata_dir: directory containing package metadata files
            named REQUIRES and NEEDED.ELF.2
    @type metadata_dir: str
    @param all_provides: a frozenset on SonameAtom instances provided by
            all installed packages
    @type all_provides: frozenset
    @rtype: list
    @return: list of tuple(filename, tuple(unresolved sonames))
    """
    try:
        with open(
            _unicode_encode(
                os.path.join(metadata_dir, "REQUIRES"),
                encoding=_encodings["fs"],
                errors="strict",
            ),
            encoding=_encodings["repo.content"],
            errors="strict",
        ) as f:
            requires = frozenset(parse_soname_deps(f.read()))
    except OSError:
        return []

    unresolved_by_category = {}
    for atom in requires:
        if atom not in all_provides:
            unresolved_by_category.setdefault(atom.multilib_category, set()).add(
                atom.soname
            )

    needed_filename = os.path.join(metadata_dir, "NEEDED.ELF.2")
    with open(
        _unicode_encode(needed_filename, encoding=_encodings["fs"], errors="strict"),
        encoding=_encodings["repo.content"],
        errors="strict",
    ) as f:
        needed = f.readlines()

    unresolved_by_file = []
    for l in needed:
        l = l.rstrip("\n")
        if not l:
            continue
        entry = NeededEntry.parse(needed_filename, l)
        missing = unresolved_by_category.get(entry.multilib_category)
        if not missing:
            continue
        # NOTE: This can contain some false positives in the case of
        # missing DT_RPATH settings, since it's possible that a subset
        # package files have the desired DT_RPATH settings. However,
        # since reported sonames are unresolved for at least some file(s),
        # false positives or this sort should not be not too annoying.
        missing = [soname for soname in entry.needed if soname in missing]
        if missing:
            unresolved_by_file.append((entry.filename, tuple(missing)))

    return unresolved_by_file
