#!/usr/bin/env python
# -*- coding: utf-8 -*-

import six

from .policy import IPolicy, pkg_id_to_version
from .policy_logger import LoggedPolicy


class UndeterminedClausePolicy(IPolicy):

    """ An IPolicy that gathers all undetermined packages from clauses whose
    truth value is not yet known and suggests them in descending order by
    package version number. """

    def __init__(self, pool, installed_repository,
                 ignore_installed_packages=None):
        if ignore_installed_packages is None:
            ignore_installed_packages = set()
        self._pool = pool

        by_version = six.functools.partial(pkg_id_to_version, self._pool)
        installed_packages = set(installed_repository)
        prefer_installed_pkgs = installed_packages - ignore_installed_packages
        self._prefer_installed_pkg_ids = sorted(
            (pool.package_id(pkg) for pkg in prefer_installed_pkgs),
            key=by_version)

        self._decision_set = set()
        self._requirements = set()
        self._all_ids = set()

    def add_requirements(self, package_ids):
        self._requirements.update(package_ids)

    def get_next_package_id(self, assignments, clauses):
        """Get the next unassigned package.
        """
        if assignments.new_keys:
            self._refresh_decision_set(assignments, clauses)

        candidate_id = None
        best = self._best_candidate

        candidate_id = self._best_sorted_candidate(
            self._prefer_installed_pkg_ids, assignments)

        if candidate_id is None:
            candidate_id = best(self._requirements, assignments)

        if candidate_id is None:
            candidate_id = best(self._decision_set, assignments, update=True)

        if candidate_id is None:
            self._refresh_decision_set(assignments, clauses)
            candidate_id = best(self._decision_set, assignments)

        if candidate_id is None:
            candidate_id = best(self._all_ids, assignments)

        assert assignments.get(candidate_id) is None, \
            "Trying to assign to a variable which is already assigned."

        return candidate_id

    def _without_assigned(self, package_ids, assignments):
        return package_ids.difference(assignments.assigned_ids)

    def _best_sorted_candidate(self, package_ids, assignments):
        for p_id in package_ids:
            if p_id not in assignments.assigned_ids:
                return p_id

    def _best_candidate(self, package_ids, assignments, update=False):
        by_version = six.functools.partial(pkg_id_to_version, self._pool)
        unassigned = self._without_assigned(package_ids, assignments)
        if update:
            package_ids.clear()
            package_ids.update(unassigned)
        try:
            return max(unassigned, key=by_version)
        except ValueError:
            return None

    def _refresh_decision_set(self, assignments, clauses):
        assignments.consume_changelog()

        all_ids = {abs(l) for c in clauses for l in c.lits}
        all_ids.update(self._prefer_installed_pkg_ids)
        self._all_ids = all_ids

        unsatisfied_clauses = {
            clause for clause in clauses
            if not any(assignments.value(l) for l in clause.lits)
        }
        self._decision_set.clear()
        self._decision_set.update(
            abs(lit)
            for clause in unsatisfied_clauses
            for lit in clause.lits
        )
        self._decision_set.difference_update(assignments.assigned_ids)


LoggedUndeterminedClausePolicy = LoggedPolicy(UndeterminedClausePolicy)
