package prolog.ast.lists

import prolog.Substitutions
import prolog.ast.terms.Term
import prolog.ast.arithmetic.Integer

sealed class List : Term {
    abstract val head: Term
    open val tail: Term? = null

    operator fun component1(): Term = head
    operator fun component2(): Term? = tail

    val size: Integer
        get() = when (this) {
            is Empty -> Integer(0)
            is Cons -> {
                val tailSize = if (tail is List) { (tail as List).size.value } else 0
                Integer(1 + tailSize)
            }
        }

    fun isEmpty(): Boolean = this is Empty

    /**
     * The empty list []
     */
    object Empty : List() {
        override val head = this
        override fun applySubstitution(subs: Substitutions): Empty = this
        override fun toString(): String = "[]"
    }

    class Cons(override val head: Term, override var tail: Term) : List() {
        override fun applySubstitution(subs: Substitutions): List {
            val newHead = head.applySubstitution(subs)
            val newTail = tail.applySubstitution(subs) as List
            return Cons(newHead, newTail)
        }

        override fun toString(): String {
            val values = mutableListOf<Term>()
            var current: Term? = this
            while (current != null && current is Cons) {
                values.add(current.head)
                current = current.tail
            }
            if (current != null && current !is Empty) values.add(current)
            return "[${values.joinToString(", ")}]"
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null) return false
        if (other !is List) return false
        if (this is Empty && other is Empty) return true
        if (this is Cons && other is Cons) {
            return this.head == other.head && this.tail == other.tail
        }
        return false
    }

    override fun hashCode(): Int = super.hashCode()
}
