package prolog.builtins

import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Goal
import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.flags.AppliedCut
import prolog.logic.applySubstitution

class Call(private val goal: Term) : Operator("call", rightOperand = goal) {
    override fun satisfy(subs: Substitutions): Answers {
        val appliedGoal = applySubstitution(goal, subs) as Goal
        return appliedGoal.satisfy(subs)
    }
}

/**
 * Make a possibly nondeterministic [Goal] semideterministic, i.e. succeed at most once.
 */
class Once(private val goal: Term) : Operator("once", rightOperand = goal) {
    private val conjunction = Conjunction(Call(goal), Cut())
    override fun satisfy(subs: Substitutions): Answers = conjunction.satisfy(subs).take(1)
}

/**
 * Calls [Goal] once, but succeeds, regardless of whether Goal succeeded or not.
 */
class Ignore(goal: Term) : Operator("ignore", rightOperand = goal) {
    private val disjunction = Disjunction(
        Conjunction(Call(goal), Cut()),
        True
    )

    override fun satisfy(subs: Substitutions): Answers = sequence {
        disjunction.satisfy(subs).forEach { result ->
            result.fold(
                onSuccess = { newSubs ->
                    yield(Result.success(newSubs))
                },
                onFailure = { failure ->
                    if (failure is AppliedCut && failure.subs != null) {
                        yield(Result.success(failure.subs))
                    } else {
                        yield(Result.failure(failure))
                    }
                }
            )
        }
    }
}
