root/src/kits/package/solver/libsolv/LibsolvSolver.cpp
/*
 * Copyright 2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 */


#include "LibsolvSolver.h"

#include <errno.h>
#include <sys/utsname.h>

#include <new>

#include <solv/policy.h>
#include <solv/poolarch.h>
#include <solv/repo.h>
#include <solv/repo_haiku.h>
#include <solv/selection.h>
#include <solv/solverdebug.h>

#include <package/PackageResolvableExpression.h>
#include <package/RepositoryCache.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverPackageSpecifier.h>
#include <package/solver/SolverPackageSpecifierList.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverRepository.h>
#include <package/solver/SolverResult.h>

#include <AutoDeleter.h>
#include <ObjectList.h>


// TODO: libsolv doesn't have any helpful out-of-memory handling. It just just
// abort()s. Obviously that isn't good behavior for a library.


BSolver*
BPackageKit::create_solver()
{
        return new(std::nothrow) LibsolvSolver;
}


struct LibsolvSolver::SolvQueue : Queue {
        SolvQueue()
        {
                queue_init(this);
        }

        ~SolvQueue()
        {
                queue_free(this);
        }
};


struct LibsolvSolver::SolvDataIterator : Dataiterator {
        SolvDataIterator(Pool* pool, Repo* repo, Id solvableId, Id keyname,
                const char* match, int flags)
        {
                dataiterator_init(this, pool, repo, solvableId, keyname, match, flags);
        }

        ~SolvDataIterator()
        {
                dataiterator_free(this);
        }
};


struct LibsolvSolver::RepositoryInfo {
        RepositoryInfo(BSolverRepository* repository)
                :
                fRepository(repository),
                fSolvRepo(NULL),
                fChangeCount(repository->ChangeCount())
        {
        }

        BSolverRepository* Repository() const
        {
                return fRepository;
        }

        Repo* SolvRepo()
        {
                return fSolvRepo;
        }

        void SetSolvRepo(Repo* repo)
        {
                fSolvRepo = repo;
        }

        bool HasChanged() const
        {
                return fChangeCount != fRepository->ChangeCount() || fSolvRepo == NULL;
        }

        void SetUnchanged()
        {
                fChangeCount = fRepository->ChangeCount();
        }

private:
        BSolverRepository*      fRepository;
        Repo*                           fSolvRepo;
        uint64                          fChangeCount;
};


struct LibsolvSolver::Problem : public BSolverProblem {
        Problem(::Id id, BType type, BSolverPackage* sourcePackage,
                BSolverPackage* targetPackage,
                const BPackageResolvableExpression& dependency)
                :
                BSolverProblem(type, sourcePackage, targetPackage, dependency),
                fId(id),
                fSelectedSolution(NULL)
        {
        }

        ::Id Id() const
        {
                return fId;
        }

        const Solution* SelectedSolution() const
        {
                return fSelectedSolution;
        }

        void SetSelectedSolution(const Solution* solution)
        {
                fSelectedSolution = solution;
        }

private:
        ::Id                    fId;
        const Solution* fSelectedSolution;
};


struct LibsolvSolver::Solution : public BSolverProblemSolution {
        Solution(::Id id, LibsolvSolver::Problem* problem)
                :
                BSolverProblemSolution(),
                fId(id),
                fProblem(problem)
        {
        }

        ::Id Id() const
        {
                return fId;
        }

        LibsolvSolver::Problem* Problem() const
        {
                return fProblem;
        }

private:
        ::Id                                    fId;
        LibsolvSolver::Problem* fProblem;
};


// #pragma mark - LibsolvSolver


LibsolvSolver::LibsolvSolver()
        :
        fPool(NULL),
        fSolver(NULL),
        fJobs(NULL),
        fRepositoryInfos(10),
        fInstalledRepository(NULL),
        fSolvablePackages(),
        fPackageSolvables(),
        fProblems(10),
        fDebugLevel(0)
{
}


LibsolvSolver::~LibsolvSolver()
{
        _Cleanup();
}


status_t
LibsolvSolver::Init()
{
        _Cleanup();

        // We do all initialization lazily.
        return B_OK;
}


void
LibsolvSolver::SetDebugLevel(int32 level)
{
        fDebugLevel = level;

        if (fPool != NULL)
                pool_setdebuglevel(fPool, fDebugLevel);
}


status_t
LibsolvSolver::AddRepository(BSolverRepository* repository)
{
        if (repository == NULL || repository->InitCheck() != B_OK)
                return B_BAD_VALUE;

        // If the repository represents installed packages, check, if we already
        // have such a repository.
        if (repository->IsInstalled() && _InstalledRepository() != NULL)
                return B_BAD_VALUE;

        // add the repository info
        RepositoryInfo* info = new(std::nothrow) RepositoryInfo(repository);
        if (info == NULL)
                return B_NO_MEMORY;

        if (!fRepositoryInfos.AddItem(info)) {
                delete info;
                return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
LibsolvSolver::FindPackages(const char* searchString, uint32 flags,
        BObjectList<BSolverPackage>& _packages)
{
        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // create data iterator
        int iteratorFlags = SEARCH_SUBSTRING;
        if ((flags & B_FIND_CASE_INSENSITIVE) != 0)
                iteratorFlags |= SEARCH_NOCASE;

        SolvDataIterator iterator(fPool, 0, 0, 0, searchString, iteratorFlags);
        SolvQueue selection;

        // search package names
        if ((flags & B_FIND_IN_NAME) != 0) {
                dataiterator_set_keyname(&iterator, SOLVABLE_NAME);
                dataiterator_set_search(&iterator, 0, 0);

                while (dataiterator_step(&iterator))
                        queue_push2(&selection, SOLVER_SOLVABLE, iterator.solvid);
        }

        // search package summaries
        if ((flags & B_FIND_IN_SUMMARY) != 0) {
                dataiterator_set_keyname(&iterator, SOLVABLE_SUMMARY);
                dataiterator_set_search(&iterator, 0, 0);

                while (dataiterator_step(&iterator))
                        queue_push2(&selection, SOLVER_SOLVABLE, iterator.solvid);
        }

        // search package description
        if ((flags & B_FIND_IN_DESCRIPTION) != 0) {
                dataiterator_set_keyname(&iterator, SOLVABLE_DESCRIPTION);
                dataiterator_set_search(&iterator, 0, 0);

                while (dataiterator_step(&iterator))
                        queue_push2(&selection, SOLVER_SOLVABLE, iterator.solvid);
        }

        // search package provides
        if ((flags & B_FIND_IN_PROVIDES) != 0) {
                dataiterator_set_keyname(&iterator, SOLVABLE_PROVIDES);
                dataiterator_set_search(&iterator, 0, 0);

                while (dataiterator_step(&iterator))
                        queue_push2(&selection, SOLVER_SOLVABLE, iterator.solvid);
        }

        // search package requires
        if ((flags & B_FIND_IN_REQUIRES) != 0) {
                dataiterator_set_keyname(&iterator, SOLVABLE_REQUIRES);
                dataiterator_set_search(&iterator, 0, 0);

                while (dataiterator_step(&iterator))
                        queue_push2(&selection, SOLVER_SOLVABLE, iterator.solvid);
        }

        return _GetFoundPackages(selection, flags, _packages);
}


status_t
LibsolvSolver::FindPackages(const BSolverPackageSpecifierList& packages,
        uint32 flags, BObjectList<BSolverPackage>& _packages,
        const BSolverPackageSpecifier** _unmatched)
{
        if (_unmatched != NULL)
                *_unmatched = NULL;

        if ((flags & B_FIND_INSTALLED_ONLY) != 0 && _InstalledRepository() == NULL)
                return B_BAD_VALUE;

        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        // add the package specifies to the job queue
        error = _AddSpecifiedPackages(packages, _unmatched,
                (flags & B_FIND_INSTALLED_ONLY) != 0 ? SELECTION_INSTALLED_ONLY : 0);
        if (error != B_OK)
                return error;

        return _GetFoundPackages(*fJobs, flags, _packages);
}


status_t
LibsolvSolver::Install(const BSolverPackageSpecifierList& packages,
        const BSolverPackageSpecifier** _unmatched)
{
        if (_unmatched != NULL)
                *_unmatched = NULL;

        if (packages.IsEmpty())
                return B_BAD_VALUE;

        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // add the packages to install to the job queue
        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        error = _AddSpecifiedPackages(packages, _unmatched, 0);
        if (error != B_OK)
                return error;

        // set jobs' solver mode and solve
        _SetJobsSolverMode(SOLVER_INSTALL);

        _InitSolver();
        return _Solve();
}


status_t
LibsolvSolver::Uninstall(const BSolverPackageSpecifierList& packages,
        const BSolverPackageSpecifier** _unmatched)
{
        if (_unmatched != NULL)
                *_unmatched = NULL;

        if (_InstalledRepository() == NULL || packages.IsEmpty())
                return B_BAD_VALUE;

        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // add the packages to uninstall to the job queue
        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        error = _AddSpecifiedPackages(packages, _unmatched,
                SELECTION_INSTALLED_ONLY);
        if (error != B_OK)
                return error;

        // set jobs' solver mode and solve
        _SetJobsSolverMode(SOLVER_ERASE);

        _InitSolver();
        solver_set_flag(fSolver, SOLVER_FLAG_ALLOW_UNINSTALL, 1);
        return _Solve();
}


status_t
LibsolvSolver::Update(const BSolverPackageSpecifierList& packages,
        bool installNotYetInstalled, const BSolverPackageSpecifier** _unmatched)
{
        if (_unmatched != NULL)
                *_unmatched = NULL;

        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // add the packages to update to the job queue -- if none are specified,
        // update all
        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        if (packages.IsEmpty()) {
                queue_push2(fJobs, SOLVER_SOLVABLE_ALL, 0);
        } else {
                error = _AddSpecifiedPackages(packages, _unmatched, 0);
                if (error != B_OK)
                        return error;
        }

        // set jobs' solver mode and solve
        _SetJobsSolverMode(SOLVER_UPDATE);

        if (installNotYetInstalled) {
                for (int i = 0; i < fJobs->count; i += 2) {
                        // change solver mode to SOLVER_INSTALL for empty update jobs
                        if (pool_isemptyupdatejob(fPool, fJobs->elements[i],
                                        fJobs->elements[i + 1])) {
                                fJobs->elements[i] &= ~SOLVER_JOBMASK;
                                fJobs->elements[i] |= SOLVER_INSTALL;
                        }
                }
        }

        _InitSolver();
        return _Solve();
}


status_t
LibsolvSolver::FullSync()
{
        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // Init the job queue and specify that all packages shall be updated.
        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        queue_push2(fJobs, SOLVER_SOLVABLE_ALL, 0);

        // set jobs' solver mode and solve
        _SetJobsSolverMode(SOLVER_DISTUPGRADE);

        _InitSolver();
        return _Solve();
}


status_t
LibsolvSolver::VerifyInstallation(uint32 flags)
{
        if (_InstalledRepository() == NULL)
                return B_BAD_VALUE;

        // add repositories to pool
        status_t error = _AddRepositories();
        if (error != B_OK)
                return error;

        // add the verify job to the job queue
        error = _InitJobQueue();
        if (error != B_OK)
                return error;

        queue_push2(fJobs, SOLVER_SOLVABLE_ALL, 0);

        // set jobs' solver mode and solve
        _SetJobsSolverMode(SOLVER_VERIFY);

        _InitSolver();
        if ((flags & B_VERIFY_ALLOW_UNINSTALL) != 0)
                solver_set_flag(fSolver, SOLVER_FLAG_ALLOW_UNINSTALL, 1);
        return _Solve();
}


status_t
LibsolvSolver::SelectProblemSolution(BSolverProblem* _problem,
        const BSolverProblemSolution* _solution)
{
        if (_problem == NULL)
                return B_BAD_VALUE;

        Problem* problem = static_cast<Problem*>(_problem);
        if (_solution == NULL) {
                problem->SetSelectedSolution(NULL);
                return B_OK;
        }

        const Solution* solution = static_cast<const Solution*>(_solution);
        if (solution->Problem() != problem)
                return B_BAD_VALUE;

        problem->SetSelectedSolution(solution);
        return B_OK;
}


status_t
LibsolvSolver::SolveAgain()
{
        if (fSolver == NULL || fJobs == NULL)
                return B_BAD_VALUE;

        // iterate through all problems and propagate the selected solutions
        int32 problemCount = fProblems.CountItems();
        for (int32 i = 0; i < problemCount; i++) {
                Problem* problem = fProblems.ItemAt(i);
                if (const Solution* solution = problem->SelectedSolution())
                        solver_take_solution(fSolver, problem->Id(), solution->Id(), fJobs);
        }

        return _Solve();
}


int32
LibsolvSolver::CountProblems() const
{
        return fProblems.CountItems();
}


BSolverProblem*
LibsolvSolver::ProblemAt(int32 index) const
{
        return fProblems.ItemAt(index);
}


status_t
LibsolvSolver::GetResult(BSolverResult& _result)
{
        if (fSolver == NULL || HasProblems())
                return B_BAD_VALUE;

        _result.MakeEmpty();

        Transaction* transaction = solver_create_transaction(fSolver);
        CObjectDeleter<Transaction, void, transaction_free>
                transactionDeleter(transaction);

        if (transaction->steps.count == 0)
                return B_OK;

        transaction_order(transaction, 0);

        for (int i = 0; i < transaction->steps.count; i++) {
                Id solvableId = transaction->steps.elements[i];
                if (fPool->installed
                        && fPool->solvables[solvableId].repo == fPool->installed) {
                        BSolverPackage* package = _GetPackage(solvableId);
                        if (package == NULL)
                                return B_ERROR;

                        if (!_result.AppendElement(
                                        BSolverResultElement(
                                                BSolverResultElement::B_TYPE_UNINSTALL, package))) {
                                return B_NO_MEMORY;
                        }
                } else {
                        BSolverPackage* package = _GetPackage(solvableId);
                        if (package == NULL)
                                return B_ERROR;

                        if (!_result.AppendElement(
                                        BSolverResultElement(
                                                BSolverResultElement::B_TYPE_INSTALL, package))) {
                                return B_NO_MEMORY;
                        }
                }
        }

        return B_OK;
}


status_t
LibsolvSolver::_InitPool()
{
        _CleanupPool();

        fPool = pool_create();

        pool_setdebuglevel(fPool, fDebugLevel);

        // Set the system architecture. We use what uname() returns unless we're on
        // x86 gcc2.
        {
                const char* arch;
                #ifdef HAIKU_TARGET_PLATFORM_HAIKU
                        #ifdef __HAIKU_ARCH_X86
                                #if (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR) == B_HAIKU_ABI_GCC_2
                                        arch = "x86_gcc2";
                                #else
                                        arch = "x86";
                                #endif
                        #else
                                struct utsname info;
                                if (uname(&info) != 0)
                                        return errno;
                                arch = info.machine;
                        #endif
                #else
                        arch = HAIKU_PACKAGING_ARCH;
                #endif

                pool_setarchpolicy(fPool, arch);
        }

        return B_OK;
}


status_t
LibsolvSolver::_InitJobQueue()
{
        _CleanupJobQueue();

        fJobs = new(std::nothrow) SolvQueue;
        return fJobs != NULL ? B_OK : B_NO_MEMORY;;
}


void
LibsolvSolver::_InitSolver()
{
        _CleanupSolver();

        fSolver = solver_create(fPool);
        solver_set_flag(fSolver, SOLVER_FLAG_SPLITPROVIDES, 1);
        solver_set_flag(fSolver, SOLVER_FLAG_BEST_OBEY_POLICY, 1);
}


void
LibsolvSolver::_Cleanup()
{
        _CleanupPool();

        fInstalledRepository = NULL;
        fRepositoryInfos.MakeEmpty();

}


void
LibsolvSolver::_CleanupPool()
{
        // clean up jobs and solver data
        _CleanupJobQueue();

        // clean up our data structures that depend on/refer to libsolv pool data
        fSolvablePackages.clear();
        fPackageSolvables.clear();

        int32 repositoryCount = fRepositoryInfos.CountItems();
        for (int32 i = 0; i < repositoryCount; i++)
                fRepositoryInfos.ItemAt(i)->SetSolvRepo(NULL);

        // delete the pool
        if (fPool != NULL) {
                pool_free(fPool);
                fPool = NULL;
        }
}


void
LibsolvSolver::_CleanupJobQueue()
{
        _CleanupSolver();

        delete fJobs;
        fJobs = NULL;
}


void
LibsolvSolver::_CleanupSolver()
{
        fProblems.MakeEmpty();

        if (fSolver != NULL) {
                solver_free(fSolver);
                fSolver = NULL;
        }
}


bool
LibsolvSolver::_HaveRepositoriesChanged() const
{
        int32 repositoryCount = fRepositoryInfos.CountItems();
        for (int32 i = 0; i < repositoryCount; i++) {
                RepositoryInfo* repositoryInfo = fRepositoryInfos.ItemAt(i);
                if (repositoryInfo->HasChanged())
                        return true;
        }

        return false;
}


status_t
LibsolvSolver::_AddRepositories()
{
        if (fPool != NULL && !_HaveRepositoriesChanged())
                return B_OK;

        // something has changed -- re-create the pool
        status_t error = _InitPool();
        if (error != B_OK)
                return error;

        fInstalledRepository = NULL;

        int32 repositoryCount = fRepositoryInfos.CountItems();
        for (int32 i = 0; i < repositoryCount; i++) {
                RepositoryInfo* repositoryInfo = fRepositoryInfos.ItemAt(i);
                BSolverRepository* repository = repositoryInfo->Repository();
                Repo* repo = repo_create(fPool, repository->Name());
                repositoryInfo->SetSolvRepo(repo);

                repo->priority = -1 - repository->Priority();
                repo->appdata = (void*)repositoryInfo;

                int32 packageCount = repository->CountPackages();
                for (int32 k = 0; k < packageCount; k++) {
                        BSolverPackage* package = repository->PackageAt(k);
                        Id solvableId = repo_add_haiku_package_info(repo, package->Info(),
                                REPO_REUSE_REPODATA | REPO_NO_INTERNALIZE);

                        try {
                                fSolvablePackages[solvableId] = package;
                                fPackageSolvables[package] = solvableId;
                        } catch (std::bad_alloc&) {
                                return B_NO_MEMORY;
                        }
                }

                repo_internalize(repo);

                if (repository->IsInstalled()) {
                        fInstalledRepository = repositoryInfo;
                        pool_set_installed(fPool, repo);
                }

                repositoryInfo->SetUnchanged();
        }

        // create "provides" lookup
        pool_createwhatprovides(fPool);

        return B_OK;
}


LibsolvSolver::RepositoryInfo*
LibsolvSolver::_InstalledRepository() const
{
        int32 repositoryCount = fRepositoryInfos.CountItems();
        for (int32 i = 0; i < repositoryCount; i++) {
                RepositoryInfo* repositoryInfo = fRepositoryInfos.ItemAt(i);
                if (repositoryInfo->Repository()->IsInstalled())
                        return repositoryInfo;
        }

        return NULL;
}


LibsolvSolver::RepositoryInfo*
LibsolvSolver::_GetRepositoryInfo(BSolverRepository* repository) const
{
        int32 repositoryCount = fRepositoryInfos.CountItems();
        for (int32 i = 0; i < repositoryCount; i++) {
                RepositoryInfo* repositoryInfo = fRepositoryInfos.ItemAt(i);
                if (repository == repositoryInfo->Repository())
                        return repositoryInfo;
        }

        return NULL;
}


BSolverPackage*
LibsolvSolver::_GetPackage(Id solvableId) const
{
        SolvableMap::const_iterator it = fSolvablePackages.find(solvableId);
        return it != fSolvablePackages.end() ? it->second : NULL;
}


Id
LibsolvSolver::_GetSolvable(BSolverPackage* package) const
{
        PackageMap::const_iterator it = fPackageSolvables.find(package);
        return it != fPackageSolvables.end() ? it->second : 0;
}


status_t
LibsolvSolver::_AddSpecifiedPackages(
        const BSolverPackageSpecifierList& packages,
        const BSolverPackageSpecifier** _unmatched, int additionalFlags)
{
        int32 packageCount = packages.CountSpecifiers();
        for (int32 i = 0; i < packageCount; i++) {
                const BSolverPackageSpecifier& specifier = *packages.SpecifierAt(i);
                switch (specifier.Type()) {
                        case BSolverPackageSpecifier::B_UNSPECIFIED:
                                return B_BAD_VALUE;

                        case BSolverPackageSpecifier::B_PACKAGE:
                        {
                                BSolverPackage* package = specifier.Package();
                                Id solvableId;
                                if (package == NULL
                                        || (solvableId = _GetSolvable(package)) == 0) {
                                        return B_BAD_VALUE;
                                }

                                queue_push2(fJobs, SOLVER_SOLVABLE, solvableId);
                                break;
                        }

                        case BSolverPackageSpecifier::B_SELECT_STRING:
                        {
                                // find matching packages
                                SolvQueue matchingPackages;

                                int flags = SELECTION_NAME | SELECTION_PROVIDES | SELECTION_GLOB
                                        | SELECTION_CANON | SELECTION_DOTARCH | SELECTION_REL
                                        | additionalFlags;
                                /*int matchFlags =*/ selection_make(fPool, &matchingPackages,
                                        specifier.SelectString().String(), flags);
                                if (matchingPackages.count == 0) {
                                        if (_unmatched != NULL)
                                                *_unmatched = &specifier;
                                        return B_NAME_NOT_FOUND;
                                }
// TODO: We might want to add support for restricting to certain repositories.
#if 0
                                // restrict to the matching repository
                                if (BSolverRepository* repository = specifier.Repository()) {
                                        RepositoryInfo* repositoryInfo
                                                = _GetRepositoryInfo(repository);
                                        if (repositoryInfo == NULL)
                                                return B_BAD_VALUE;

                                        SolvQueue repoFilter;
                                        queue_push2(&repoFilter,
                                                SOLVER_SOLVABLE_REPO
                                                        /* | SOLVER_SETREPO | SOLVER_SETVENDOR*/,
                                                repositoryInfo->SolvRepo()->repoid);

                                        selection_filter(fPool, &matchingPackages, &repoFilter);

                                        if (matchingPackages.count == 0)
                                                return B_NAME_NOT_FOUND;
                                }
#endif

                                for (int j = 0; j < matchingPackages.count; j++)
                                        queue_push(fJobs, matchingPackages.elements[j]);
                        }
                }
        }

        return B_OK;
}


status_t
LibsolvSolver::_AddProblem(Id problemId)
{
        enum {
                NEED_SOURCE             = 0x1,
                NEED_TARGET             = 0x2,
                NEED_DEPENDENCY = 0x4
        };

        Id ruleId = solver_findproblemrule(fSolver, problemId);
        Id sourceId;
        Id targetId;
        Id dependencyId;
        BSolverProblem::BType problemType = BSolverProblem::B_UNSPECIFIED;
        uint32 needed = 0;

        switch (solver_ruleinfo(fSolver, ruleId, &sourceId, &targetId,
                        &dependencyId)) {
                case SOLVER_RULE_DISTUPGRADE:
                        problemType = BSolverProblem::B_NOT_IN_DISTUPGRADE_REPOSITORY;
                        needed = NEED_SOURCE;
                        break;
                case SOLVER_RULE_INFARCH:
                        problemType = BSolverProblem::B_INFERIOR_ARCHITECTURE;
                        needed = NEED_SOURCE;
                        break;
                case SOLVER_RULE_UPDATE:
                        problemType = BSolverProblem::B_INSTALLED_PACKAGE_PROBLEM;
                        needed = NEED_SOURCE;
                        break;
                case SOLVER_RULE_JOB:
                        problemType = BSolverProblem::B_CONFLICTING_REQUESTS;
                        break;
                case SOLVER_RULE_JOB_NOTHING_PROVIDES_DEP:
                        problemType = BSolverProblem::B_REQUESTED_RESOLVABLE_NOT_PROVIDED;
                        needed = NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_JOB_PROVIDED_BY_SYSTEM:
                        problemType
                                = BSolverProblem::B_REQUESTED_RESOLVABLE_PROVIDED_BY_SYSTEM;
                        needed = NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM:
                        problemType = BSolverProblem::B_DEPENDENCY_PROBLEM;
                        break;
                case SOLVER_RULE_RPM_NOT_INSTALLABLE:
                        problemType = BSolverProblem::B_PACKAGE_NOT_INSTALLABLE;
                        needed = NEED_SOURCE;
                        break;
                case SOLVER_RULE_RPM_NOTHING_PROVIDES_DEP:
                        problemType = BSolverProblem::B_DEPENDENCY_NOT_PROVIDED;
                        needed = NEED_SOURCE | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_SAME_NAME:
                        problemType = BSolverProblem::B_PACKAGE_NAME_CLASH;
                        needed = NEED_SOURCE | NEED_TARGET;
                        break;
                case SOLVER_RULE_RPM_PACKAGE_CONFLICT:
                        problemType = BSolverProblem::B_PACKAGE_CONFLICT;
                        needed = NEED_SOURCE | NEED_TARGET | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_PACKAGE_OBSOLETES:
                        problemType = BSolverProblem::B_PACKAGE_OBSOLETES_RESOLVABLE;
                        needed = NEED_SOURCE | NEED_TARGET | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_INSTALLEDPKG_OBSOLETES:
                        problemType
                                = BSolverProblem::B_INSTALLED_PACKAGE_OBSOLETES_RESOLVABLE;
                        needed = NEED_SOURCE | NEED_TARGET | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_IMPLICIT_OBSOLETES:
                        problemType
                                = BSolverProblem::B_PACKAGE_IMPLICITLY_OBSOLETES_RESOLVABLE;
                        needed = NEED_SOURCE | NEED_TARGET | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_PACKAGE_REQUIRES:
                        problemType = BSolverProblem::B_DEPENDENCY_NOT_INSTALLABLE;
                        needed = NEED_SOURCE | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_RPM_SELF_CONFLICT:
                        problemType = BSolverProblem::B_SELF_CONFLICT;
                        needed = NEED_SOURCE | NEED_DEPENDENCY;
                        break;
                case SOLVER_RULE_UNKNOWN:
                case SOLVER_RULE_FEATURE:
                case SOLVER_RULE_LEARNT:
                case SOLVER_RULE_CHOICE:
                case SOLVER_RULE_BEST:
                        problemType = BSolverProblem::B_UNSPECIFIED;
                        break;
        }

        BSolverPackage* sourcePackage = NULL;
        if ((needed & NEED_SOURCE) != 0) {
                sourcePackage = _GetPackage(sourceId);
                if (sourcePackage == NULL)
                        return B_ERROR;
        }

        BSolverPackage* targetPackage = NULL;
        if ((needed & NEED_TARGET) != 0) {
                targetPackage = _GetPackage(targetId);
                if (targetPackage == NULL)
                        return B_ERROR;
        }

        BPackageResolvableExpression dependency;
        if ((needed & NEED_DEPENDENCY) != 0) {
                status_t error = _GetResolvableExpression(dependencyId, dependency);
                if (error != B_OK)
                        return error;
        }

        Problem* problem = new(std::nothrow) Problem(problemId, problemType,
                sourcePackage, targetPackage, dependency);
        if (problem == NULL || !fProblems.AddItem(problem)) {
                delete problem;
                return B_NO_MEMORY;
        }

        int solutionCount = solver_solution_count(fSolver, problemId);
        for (Id solutionId = 1; solutionId <= solutionCount; solutionId++) {
                status_t error = _AddSolution(problem, solutionId);
                if (error != B_OK)
                        return error;
        }

        return B_OK;
}


status_t
LibsolvSolver::_AddSolution(Problem* problem, Id solutionId)
{
        Solution* solution = new(std::nothrow) Solution(solutionId, problem);
        if (solution == NULL || !problem->AppendSolution(solution)) {
                delete solution;
                return B_NO_MEMORY;
        }

        Id elementId = 0;
        for (;;) {
                Id sourceId;
                Id targetId;
                elementId = solver_next_solutionelement(fSolver, problem->Id(),
                        solutionId, elementId, &sourceId, &targetId);
                if (elementId == 0)
                        break;

                status_t error = _AddSolutionElement(solution, sourceId, targetId);
                if (error != B_OK)
                        return error;
        }

        return B_OK;
}


status_t
LibsolvSolver::_AddSolutionElement(Solution* solution, Id sourceId, Id targetId)
{
        typedef BSolverProblemSolutionElement Element;

        if (sourceId == SOLVER_SOLUTION_JOB
                || sourceId == SOLVER_SOLUTION_POOLJOB) {
                // targetId is an index into the job queue
                if (sourceId == SOLVER_SOLUTION_JOB)
                        targetId += fSolver->pooljobcnt;

                Id how = fSolver->job.elements[targetId - 1];
                Id what = fSolver->job.elements[targetId];
                Id select = how & SOLVER_SELECTMASK;

                switch (how & SOLVER_JOBMASK) {
                        case SOLVER_INSTALL:
                                if (select == SOLVER_SOLVABLE && fInstalledRepository != NULL
                                        && fPool->solvables[what].repo
                                                == fInstalledRepository->SolvRepo()) {
                                        return _AddSolutionElement(solution, Element::B_DONT_KEEP,
                                                what, 0, NULL);
                                }

                                return _AddSolutionElement(solution,
                                        Element::B_DONT_INSTALL, 0, 0,
                                        solver_select2str(fPool, select, what));

                        case SOLVER_ERASE:
                        {
                                if (select == SOLVER_SOLVABLE
                                        && (fInstalledRepository == NULL
                                                || fPool->solvables[what].repo
                                                        != fInstalledRepository->SolvRepo())) {
                                        return _AddSolutionElement(solution,
                                                Element::B_DONT_FORBID_INSTALLATION, what, 0, NULL);
                                }

                                Element::BType type = select == SOLVER_SOLVABLE_PROVIDES
                                        ? Element::B_DONT_DEINSTALL_ALL : Element::B_DONT_DEINSTALL;
                                return _AddSolutionElement(solution, type, 0, 0,
                                        solver_select2str(fPool, select, what));
                        }

                        case SOLVER_UPDATE:
                                return _AddSolutionElement(solution,
                                        Element::B_DONT_INSTALL_MOST_RECENT, 0, 0,
                                        solver_select2str(fPool, select, what));

                        case SOLVER_LOCK:
                                return _AddSolutionElement(solution, Element::B_DONT_LOCK, 0, 0,
                                        solver_select2str(fPool, select, what));

                        default:
                                return _AddSolutionElement(solution, Element::B_UNSPECIFIED, 0,
                                        0, NULL);
                }
        }

        Solvable* target = targetId != 0 ? fPool->solvables + targetId : NULL;
        bool targetInstalled = target != NULL && fInstalledRepository
                && target->repo == fInstalledRepository->SolvRepo();

        if (sourceId == SOLVER_SOLUTION_INFARCH) {
                return _AddSolutionElement(solution,
                        targetInstalled
                                ? Element::B_KEEP_INFERIOR_ARCHITECTURE
                                : Element::B_INSTALL_INFERIOR_ARCHITECTURE,
                        targetId, 0, NULL);
        }

        if (sourceId == SOLVER_SOLUTION_DISTUPGRADE) {
                return _AddSolutionElement(solution,
                        targetInstalled
                                ? Element::B_KEEP_EXCLUDED : Element::B_INSTALL_EXCLUDED,
                        targetId, 0, NULL);
        }

        if (sourceId == SOLVER_SOLUTION_BEST) {
                return _AddSolutionElement(solution,
                        targetInstalled ? Element::B_KEEP_OLD : Element::B_INSTALL_OLD,
                        targetId, 0, NULL);
        }

        // replace source with target
        Solvable* source = fPool->solvables + sourceId;
        if (target == NULL) {
                return _AddSolutionElement(solution, Element::B_ALLOW_DEINSTALLATION,
                        sourceId, 0, NULL);
        }

        int illegalMask = policy_is_illegal(fSolver, source, target, 0);
        if ((illegalMask & POLICY_ILLEGAL_DOWNGRADE) != 0) {
                status_t error = _AddSolutionElement(solution,
                        Element::B_ALLOW_DOWNGRADE, sourceId, targetId, NULL);
                if (error != B_OK)
                        return error;
        }

        if ((illegalMask & POLICY_ILLEGAL_NAMECHANGE) != 0) {
                status_t error = _AddSolutionElement(solution,
                        Element::B_ALLOW_NAME_CHANGE, sourceId, targetId, NULL);
                if (error != B_OK)
                        return error;
        }

        if ((illegalMask & POLICY_ILLEGAL_ARCHCHANGE) != 0) {
                status_t error = _AddSolutionElement(solution,
                        Element::B_ALLOW_ARCHITECTURE_CHANGE, sourceId, targetId, NULL);
                if (error != B_OK)
                        return error;
        }

        if ((illegalMask & POLICY_ILLEGAL_VENDORCHANGE) != 0) {
                status_t error = _AddSolutionElement(solution,
                        Element::B_ALLOW_VENDOR_CHANGE, sourceId, targetId, NULL);
                if (error != B_OK)
                        return error;
        }

        if (illegalMask == 0) {
                return _AddSolutionElement(solution, Element::B_ALLOW_REPLACEMENT,
                        sourceId, targetId, NULL);
        }

        return B_OK;
}


status_t
LibsolvSolver::_AddSolutionElement(Solution* solution,
        BSolverProblemSolutionElement::BType type, Id sourceSolvableId,
        Id targetSolvableId, const char* selectionString)
{
        BSolverPackage* sourcePackage = NULL;
        if (sourceSolvableId != 0) {
                sourcePackage = _GetPackage(sourceSolvableId);
                if (sourcePackage == NULL)
                        return B_ERROR;
        }

        BSolverPackage* targetPackage = NULL;
        if (targetSolvableId != 0) {
                targetPackage = _GetPackage(targetSolvableId);
                if (targetPackage == NULL)
                        return B_ERROR;
        }

        BString selection;
        if (selectionString != NULL && selectionString[0] != '\0') {
                selection = selectionString;
                if (selection.IsEmpty())
                        return B_NO_MEMORY;
        }

        if (!solution->AppendElement(BSolverProblemSolutionElement(
                        type, sourcePackage, targetPackage, selection))) {
                return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
LibsolvSolver::_GetResolvableExpression(Id id,
        BPackageResolvableExpression& _expression) const
{
        // Try to translate the libsolv ID to a resolvable expression. Generally
        // that doesn't work, since libsolv is more expressive, but all the stuff
        // we feed libsolv we should be able to translate back.

        if (!ISRELDEP(id)) {
                // just a string
                _expression.SetTo(pool_id2str(fPool, id));
                return B_OK;
        }

        // a composite -- analyze it
        Reldep* reldep = GETRELDEP(fPool, id);

        // No support for more than one level, so both name and evr must be strings.
        if (ISRELDEP(reldep->name) || ISRELDEP(reldep->evr))
                return B_NOT_SUPPORTED;

        const char* name = pool_id2str(fPool, reldep->name);
        const char* versionString = pool_id2str(fPool, reldep->evr);
        if (name == NULL || versionString == NULL)
                return B_NOT_SUPPORTED;

        // get the operator -- we don't support all libsolv supports
        BPackageResolvableOperator op;
        switch (reldep->flags) {
                case 1:
                        op = B_PACKAGE_RESOLVABLE_OP_GREATER;
                        break;
                case 2:
                        op = B_PACKAGE_RESOLVABLE_OP_EQUAL;
                        break;
                case 3:
                        op = B_PACKAGE_RESOLVABLE_OP_GREATER_EQUAL;
                        break;
                case 4:
                        op = B_PACKAGE_RESOLVABLE_OP_LESS;
                        break;
                case 5:
                        op = B_PACKAGE_RESOLVABLE_OP_NOT_EQUAL;
                        break;
                case 6:
                        op = B_PACKAGE_RESOLVABLE_OP_LESS_EQUAL;
                        break;
                default:
                        return B_NOT_SUPPORTED;
        }

        // get the version (cut off the empty epoch)
        if (versionString[0] == ':')
                versionString++;

        BPackageVersion version;
        status_t error = version.SetTo(versionString, true);
        if (error != B_OK)
                return error == B_BAD_DATA ? B_NOT_SUPPORTED : error;

        _expression.SetTo(name, op, version);
        return B_OK;
}


status_t
LibsolvSolver::_GetFoundPackages(SolvQueue& selection, uint32 flags,
        BObjectList<BSolverPackage>& _packages)
{
        // get solvables
        SolvQueue solvables;
        selection_solvables(fPool, &selection, &solvables);

        // get packages
        for (int i = 0; i < solvables.count; i++) {
                BSolverPackage* package = _GetPackage(solvables.elements[i]);
                if (package == NULL)
                        return B_ERROR;

                // TODO: Fix handling of SELECTION_INSTALLED_ONLY in libsolv. Despite
                // passing the flag, we get solvables that aren't installed.
                if ((flags & B_FIND_INSTALLED_ONLY) != 0
                        && package->Repository() != fInstalledRepository->Repository()) {
                        continue;
                }

                if (!_packages.AddItem(package))
                        return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
LibsolvSolver::_Solve()
{
        if (fJobs == NULL || fSolver == NULL)
                return B_BAD_VALUE;

        int problemCount = solver_solve(fSolver, fJobs);

        // get the problems (if any)
        fProblems.MakeEmpty();

        for (Id problemId = 1; problemId <= problemCount; problemId++) {
                status_t error = _AddProblem(problemId);
                if (error != B_OK)
                        return error;
        }

        return B_OK;
}


void
LibsolvSolver::_SetJobsSolverMode(int solverMode)
{
        for (int i = 0; i < fJobs->count; i += 2)
                fJobs->elements[i] |= solverMode;
}