#!/usr/bin/python3.11
# Copyright 1999-2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import sys
from os import path as osp

if osp.isfile(
    osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), ".portage_not_installed")
):
    sys.path.insert(
        0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "lib")
    )
import portage

portage._internal_caller = True
from portage import os
from portage._sets.files import StaticFileSet, WorldSelectedPackagesSet

import re
import tempfile
import textwrap

__candidatematcher__ = re.compile("^[0-9]+: \\*\\*\\* emerge ")
__noncandidatematcher__ = re.compile(
    " sync( |$)| clean( |$)| search( |$)|--oneshot|--fetchonly| unmerge( |$)"
)


def issyspkg(pkgline):
    return pkgline[0] == "*"


def iscandidate(logline):
    return __candidatematcher__.match(logline) and not __noncandidatematcher__.search(
        logline
    )


def getpkginfo(logline):
    logline = re.sub("^[0-9]+: \\*\\*\\* emerge ", "", logline)
    logline = logline.strip()
    logline = re.sub("(\\S+\\.(ebuild|tbz2))|(--\\S+)|inject ", "", logline)
    return logline.strip()


__uniqlist__ = []


def isunwanted(pkgline):
    if pkgline in ["world", "system", "depclean", "info", "regen", ""]:
        return False
    elif pkgline in __uniqlist__:
        return False
    elif not re.search("^[a-zA-Z<>=~]", pkgline):
        return False
    else:
        __uniqlist__.append(pkgline)
        return True


eroot = portage.settings["EROOT"]
world_file = os.path.join(eroot, portage.WORLD_FILE)

# show a little description if we have arguments
if len(sys.argv) >= 2 and sys.argv[1] in ["-h", "--help"]:
    print("This script regenerates the portage world file by checking the portage")
    print("logfile for all actions that you've done in the past. It ignores any")
    print("arguments except --help. It is recommended that you make a backup of")
    print(f"your existing world file ({world_file}) before using this tool.")
    sys.exit(0)

worldlist = portage.grabfile(world_file)
syslist = [x for x in portage.settings.packages if issyspkg(x)]

if portage.settings.get("EMERGE_LOG_DIR"):
    logfile = portage.grabfile(
        os.path.join(portage.settings["EMERGE_LOG_DIR"], "emerge.log")
    )
else:
    logfile = portage.grabfile(os.path.join(eroot, "var/log/emerge.log"))
biglist = [getpkginfo(x) for x in logfile if iscandidate(x)]
tmplist = []
for l in biglist:
    tmplist += l.split()
biglist = [x for x in tmplist if isunwanted(x)]
# for p in biglist:
# 	print(p)
# sys.exit(0)

# resolving virtuals
realsyslist = []
for mykey in syslist:
    # drop the asterix
    mykey = mykey[1:]
    # print("candidate:",mykey)
    mylist = portage.db[eroot]["vartree"].dbapi.match(mykey)
    if mylist:
        mykey = portage.cpv_getkey(mylist[0])
        if mykey not in realsyslist:
            realsyslist.append(mykey)

for mykey in biglist:
    # print("checking:",mykey)
    try:
        mylist = portage.db[eroot]["vartree"].dbapi.match(mykey)
    except (portage.exception.InvalidAtom, KeyError):
        if "--debug" in sys.argv:
            print(f"* ignoring broken log entry for {mykey} (likely injected)")
    except ValueError as e:
        try:
            print(f"* {mykey} is an ambiguous package name, candidates are:\n{e}")
        except AttributeError:
            # FIXME: Find out what causes this (bug #344845).
            print(f"* {mykey} is an ambiguous package name")
        continue
    if mylist:
        # print "mylist:",mylist
        myfavkey = portage.cpv_getkey(mylist[0])
        if (myfavkey not in realsyslist) and (myfavkey not in worldlist):
            print("add to world:", myfavkey)
            worldlist.append(myfavkey)

if not worldlist:
    pass
else:
    existing_set = WorldSelectedPackagesSet(eroot)
    existing_set.load()

    if not existing_set:
        existing_set.replace(worldlist)
    else:
        old_world = existing_set._filename
        fd, tmp_filename = tempfile.mkstemp(
            suffix=".tmp",
            prefix=os.path.basename(old_world) + ".",
            dir=os.path.dirname(old_world),
        )
        os.close(fd)

        new_set = StaticFileSet(tmp_filename)
        new_set.update(worldlist)

        if existing_set.getAtoms() == new_set.getAtoms():
            os.unlink(tmp_filename)
        else:
            new_set.write()

            msg = (
                "Please review differences between old and new files, "
                + "and replace the old file if desired."
            )

            portage.util.writemsg_stdout("\n", noiselevel=-1)
            for line in textwrap.wrap(msg, 65):
                portage.util.writemsg_stdout(f"{line}\n", noiselevel=-1)
            portage.util.writemsg_stdout("\n", noiselevel=-1)
            portage.util.writemsg_stdout(f"  old: {old_world}\n\n", noiselevel=-1)
            portage.util.writemsg_stdout(f"  new: {tmp_filename}\n\n", noiselevel=-1)
