package net.cijber.pubgrub import net.cijber.pubgrub.packages.PackageSelection import net.cijber.pubgrub.packages.PackageVersion import net.cijber.pubgrub.stubs.PackageId import net.cijber.pubgrub.stubs.PackageRetriever import net.cijber.pubgrub.stubs.Version import net.cijber.pubgrub.version.VersionRange class Solver

>( val packageRetriever: PackageRetriever, private val root: PackageVersion ) { private val incompatibilities: MutableMap>> = mutableMapOf( root.pkg to mutableListOf( Incompatibility( listOf(Term(PackageSelection(root.pkg, VersionRange.single(root.version)), true)), IncompatibilityCause.Simple(IncompatibilityCause.SimpleCause.Root) ) ) ) private val solution = PartialSolution() suspend fun solve() { var next: P? = root.pkg while (next != null) { propagate(next) next = choosePackageVersion() } } private fun propagate(pkg: P) { val changed = mutableSetOf(pkg) while (changed.isNotEmpty()) { val currentPkg = changed.first() changed.remove(currentPkg) for (incompat in incompatibilities.getOrPut(currentPkg, ::mutableListOf).reversed()) { val result = propagateIncompatibility(incompat) if (result == PropagationResult.Conflict) { val rootCause = resolveConflict(incompat) changed.clear() @Suppress("UNCHECKED_CAST") changed.add((propagateIncompatibility(rootCause) as? PropagationResult.Unsatisfied

)!!.pkg) break } if (result is PropagationResult.Unsatisfied<*>) { @Suppress("UNCHECKED_CAST") changed.add(result.pkg as P) } } } } private fun resolveConflict(incompat: Incompatibility): Incompatibility { var workingIncompatibility = incompat var newIncompatibility = false while (!incompat.isFailure) { var mostRecentTerm: Term? = null var mostRecentSatisfier: Assignment? = null var difference: Term? = null var previousSatisfierLevel = 1 for (term in workingIncompatibility.terms) { val satisfier = solution.satisfier(term) if (mostRecentSatisfier == null) { mostRecentTerm = term mostRecentSatisfier = satisfier } else if (mostRecentSatisfier.index < satisfier.index) { previousSatisfierLevel = maxOf(previousSatisfierLevel, mostRecentSatisfier.decisionLevel) mostRecentTerm = term; mostRecentSatisfier = satisfier; difference = null; } else { previousSatisfierLevel = maxOf(previousSatisfierLevel, satisfier.decisionLevel) } if (mostRecentTerm == term) { difference = mostRecentSatisfier.difference(mostRecentTerm) if (difference != null) { previousSatisfierLevel = maxOf( previousSatisfierLevel, solution.satisfier(difference.inverse).decisionLevel ) } } } if (previousSatisfierLevel < mostRecentSatisfier!!.decisionLevel || mostRecentSatisfier.cause == null ) { solution.backtrack(previousSatisfierLevel); if (newIncompatibility) { addIncompatibility(workingIncompatibility) } return workingIncompatibility } val newTerms = workingIncompatibility.terms.filter { it != mostRecentTerm }.toMutableList() mostRecentSatisfier.cause?.let { newTerms.addAll(it.terms.filter { term -> term.pkg != mostRecentSatisfier.pkg }) } if (difference != null) { newTerms.add(difference.inverse) } workingIncompatibility = Incompatibility( newTerms, IncompatibilityCause.ConflictCause(workingIncompatibility, mostRecentSatisfier.cause!!) ) } error("Failed") } private fun addIncompatibility(incompat: Incompatibility) { for (term in incompat.terms) { incompatibilities.getOrPut(term.pkg, { mutableListOf() }).add(incompat) } } private fun propagateIncompatibility(incompatibility: Incompatibility): PropagationResult { val unsatisfied = run { var unsatisfied: Term? = null; for (term in incompatibility.terms) { when (val relation = solution.relation(term)) { SetRelation.Disjoint -> return PropagationResult.None SetRelation.Overlaps, SetRelation.Subset, SetRelation.Superset -> { if (unsatisfied != null) { return PropagationResult.None } unsatisfied = term } } } unsatisfied } ?: return PropagationResult.Conflict solution.derive(unsatisfied.pkgSelection, !unsatisfied.exclusive, incompatibility) return PropagationResult.Unsatisfied(unsatisfied.pkg) } private sealed class PropagationResult { object None : PropagationResult() object Conflict : PropagationResult() class Unsatisfied

(val pkg: P) : PropagationResult() } private fun choosePackageVersion(): P? { return null } }