package prolog.builtins

import prolog.Answers
import prolog.Substitutions
import prolog.ast.lists.List
import prolog.ast.lists.List.Cons
import prolog.ast.lists.List.Empty
import prolog.ast.terms.Body
import prolog.ast.terms.Operator
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.logic.applySubstitution
import prolog.logic.unifyLazy

class Member(private val element: Term, private val list: Term) : Operator("member", element, list) {
    private var solution: Body = when (list) {
        is Empty -> Fail
        is Cons -> Disjunction(
            Unify(element, list.head),
            Member(element, list.tail)
        )

        else -> {
            // Generate a sequence of lists that have anonymous variables in them.
            // Place element at each index
            // TODO Fix this
            Unify(element, list)
        }
    }

    override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs)

    override fun applySubstitution(subs: Substitutions): Member = Member(
        applySubstitution(element, subs),
        applySubstitution(list, subs) as List
    )

    override fun toString(): String = "$element ∈ $list"
}

class Append(private val list1: Term, private val list2: Term, private val list3: Term) :
    Structure("append", list1, list2, list3) {
    override fun satisfy(subs: Substitutions): Answers = sequence {
        val l1 = applySubstitution(list1, subs)
        val l2 = applySubstitution(list2, subs)
        val l3 = applySubstitution(list3, subs)

        if (l1 is Empty) {
            yieldAll(unifyLazy(l2, l3, subs))
        }

        val head1 = if (l1 is Cons) l1.head else Variable("Head1")
        val tail1 = if (l1 is Cons) l1.tail else Variable("Tail1")
        val head3 = if (l3 is Cons) l3.head else Variable("Head3")
        val tail3 = if (l3 is Cons) l3.tail else Variable("Tail3")

        unifyLazy(head1, head3, subs).forEach { headResult ->
            headResult.map { headSubs ->
                val newSubs = subs + headSubs
                Append(tail1, list2, tail3).satisfy(newSubs).forEach { tailResult ->
                    tailResult.map { tailSubs ->
                        val result = headSubs + tailSubs
                        yield(Result.success(result))
                    }
                }
            }
        }

        TODO("This does not return the expected results, especially when using variables for the lists.")
    }

    override fun applySubstitution(subs: Substitutions): Append = Append(
        applySubstitution(list1, subs),
        applySubstitution(list2, subs),
        applySubstitution(list3, subs)
    )
}
