# Copyright 2013-2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import itertools
import logging
import stat

from portage import os
from portage.package.ebuild.fetch import DistfileName
from .DeletionTask import DeletionTask


class DeletionIterator:
    def __init__(self, config):
        self._config = config

    def __iter__(self):
        distdir = self._config.options.distfiles
        file_owners = self._config.file_owners
        whitelist = self._config.whitelist
        distfiles_local = self._config.options.distfiles_local
        deletion_db = self._config.deletion_db
        deletion_delay = self._config.options.deletion_delay
        start_time = self._config.start_time
        distfiles_set = set()
        distfiles_set.update(
            (
                filename
                if isinstance(filename, DistfileName)
                else DistfileName(filename)
                for filename in itertools.chain.from_iterable(
                    layout.get_filenames(distdir) for layout in self._config.layouts
                )
            )
            if self._config.content_db is None
            else itertools.chain.from_iterable(
                (
                    self._config.content_db.get_filenames_translate(filename)
                    for filename in itertools.chain.from_iterable(
                        layout.get_filenames(distdir) for layout in self._config.layouts
                    )
                )
            )
        )
        for filename in distfiles_set:
            # require at least one successful stat()
            exceptions = []
            for layout in reversed(self._config.layouts):
                path = os.path.join(distdir, layout.get_path(filename))
                try:
                    st = os.stat(path)
                except OSError as e:
                    # is it a dangling symlink?
                    try:
                        if os.path.islink(path):
                            os.unlink(path)
                    except OSError as e:
                        exceptions.append(e)
                else:
                    if stat.S_ISREG(st.st_mode):
                        break
            else:
                if exceptions:
                    logging.error(
                        "stat failed on '%s' in distfiles: %s\n"
                        % (filename, "; ".join(str(x) for x in exceptions))
                    )
                continue

            if filename in file_owners:
                if deletion_db is not None:
                    try:
                        del deletion_db[filename]
                    except KeyError:
                        pass
            elif whitelist is not None and filename in whitelist:
                if deletion_db is not None:
                    try:
                        del deletion_db[filename]
                    except KeyError:
                        pass
            elif distfiles_local is not None and os.path.exists(
                os.path.join(distfiles_local, filename)
            ):
                if deletion_db is not None:
                    try:
                        del deletion_db[filename]
                    except KeyError:
                        pass
            else:
                self._config.scheduled_deletion_count += 1

                if deletion_db is None or deletion_delay is None:

                    yield DeletionTask(
                        background=True,
                        distfile=filename,
                        distfile_path=path,
                        config=self._config,
                    )

                else:
                    deletion_entry = deletion_db.get(filename)

                    if deletion_entry is None:
                        logging.debug("add '%s' to deletion db" % filename)
                        deletion_db[filename] = start_time

                    elif deletion_entry + deletion_delay <= start_time:

                        yield DeletionTask(
                            background=True,
                            distfile=filename,
                            distfile_path=path,
                            config=self._config,
                        )

        if deletion_db is not None:
            for filename in list(deletion_db):
                if filename not in distfiles_set:
                    try:
                        del deletion_db[filename]
                    except KeyError:
                        pass
                    else:
                        logging.debug("drop '%s' from deletion db" % filename)
