package me.eater.threedom.dom

import me.eater.threedom.utils.joml.times
import org.joml.Matrix4d
import org.joml.Matrix4dc
import java.util.concurrent.atomic.AtomicLong

interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
    val className: String
        get() = this::class.java.name

    /**
     * The ID of this node
     */
    var id: String?

    /**
     * Set with all class names assigned to this node
     */
    val tagList: MutableSet<String>

    /**
     * Internal ID of this node, should be unique inside document
     */
    val nodeId: Long

    /**
     * Parent of this node
     */
    val parentNode: INode<*>?

    /**
     * Document this node belongs to
     */
    val document: IDocument?

    /**
     * If the location should be indexed
     */
    val shouldIndexLocation: Boolean
        get() = false

    /**
     * Absolute matrix relative to document
     */
    val absolute: Matrix4dc
        get() = (parentNode?.absolute ?: Matrix4d()) * model

    /**
     * model matrix relative to parent node
     */
    var model: Matrix4dc

    /**
     * Clone this node
     *
     * @param deep also clone all child nodes
     */
    fun clone(deep: Boolean = false): T

    /**
     * Update the parent of this node, for consistency purposes.
     *
     * @internal
     */
    fun updateParent(refNode: INode<*>?): Boolean

    /**
     * Detach this node from the owner document
     */
    fun detachFromDocument()

    /**
     * Check if [node] is parent or grand-parent of this node
     */
    fun hasParent(node: INode<*>): Boolean {
        var current: INode<*>? = parentNode;

        while (current != null) {
            if (node.nodeId == current.nodeId) {
                return true
            }

            current = current.parentNode
        }

        return false
    }

    override fun compareTo(other: INode<*>): Int = this.nodeId.compareTo(other.nodeId)

    /**
     * Update the absolute position of this node
     * @internal
     */
    fun updateAbsolute()

    companion object {
        private val atomicNodeId = AtomicLong(0)
        fun getNextNodeId() = atomicNodeId.getAndIncrement()
    }
}
