package prolog.logic

import prolog.Substitutions
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable

/**
 * Unify the free variables in Term with a term $VAR(N), where N is the number of the variable.
 * Counting starts at Start.
 * End is unified with the number that should be given to the next variable.
 *
 * Source: [SWI-Prolog Predicate numbervars/3](https://www.swi-prolog.org/pldoc/man?predicate=numbervars/3)
 *
 * @return Pair of the next number and only the new substitutions of variables to $VAR(N)
 */
fun numbervars(
    term: Term,
    start: Int = 0,
    subs: Substitutions = emptyMap(),
    sessionSubs: Substitutions = emptyMap()
): Pair<Int, Substitutions> {
    when {
        variable(term, subs) -> {
            // All instances of the same variable are unified with the same term
            if (term in sessionSubs) {
                return Pair(start, emptyMap())
            }

            val from = term as Variable
            var suggestedName = "${from.name}@$start"
            // If the suggested name is already in use, find a new one
            while ((subs + sessionSubs).any { (it.key as Variable).name == suggestedName }) {
                val randomInfix = ((0..9) + ('a'..'z') + ('A'..'Z')).random()
                suggestedName = "${from.name}@${randomInfix}_($start)"
            }
            return Pair(start + 1, mapOf(from to Variable(suggestedName)))
        }

        compound(term, subs) -> {
            val from = applySubstitution(term, subs) as Structure
            var n = start
            val s: MutableMap<Term, Term> = sessionSubs.toMutableMap()
            from.arguments.forEach { arg ->
                val (newN, newSubs) = numbervars(arg, n, subs, s)
                n = newN
                s += newSubs
            }
            return Pair(n, s)
        }

        else -> {
            return Pair(start, emptyMap())
        }
    }
}
