package me.eater.threedom.dom import me.eater.threedom.dom.event.DOMTreeUpdate import me.eater.threedom.dom.event.NodeClassListUpdate import me.eater.threedom.dom.event.NodeIDUpdate import me.eater.threedom.event.Event import me.eater.threedom.generated.EventNames import kotlin.reflect.KClass class Document : IDocument { val root: PlainNode = PlainNode(this) private val eventTree = EventTree { on { (event) -> removeNodeFromSearch(event.child) } on { (event) -> addNodeToSearch(event.child) } on { (event) -> event.old?.let { byId.remove(it, event.node.nodeId) } event.new?.let { byId.putIfAbsent(it, event.node.nodeId) } } on { (event) -> for (className in event.classNames) { byClass.getOrPut(className, ::mutableSetOf).add(event.node.nodeId) } } on { (event) -> for (className in event.classNames) { val set = byClass[className] ?: continue set.remove(event.node.nodeId) if (set.size == 0) { byClass.remove(className) } } } } private val allNodes: MutableMap> = mutableMapOf() private val byId: MutableMap = mutableMapOf() private val byClass: MutableMap> = mutableMapOf() override fun addEventListener(eventName: String, refNode: INode<*>, block: (Event) -> Unit) { @Suppress("UNCHECKED_CAST") eventTree.addEventListener(eventName, refNode, block as (Event<*>) -> Unit) } fun addEventListener(eventName: String, block: (Event) -> Unit) = addEventListener(eventName, root, block) fun addTopLevelEventListener(eventName: String, block: (Event) -> Unit) = @Suppress("UNCHECKED_CAST") eventTree.addTopLevelEventListener(eventName, block as (Event<*>) -> Unit) override fun trigger(eventName: String, targetNode: INode<*>, event: Event<*>) = eventTree.trigger(eventName, targetNode, event) override fun addNode(newNode: INode<*>) = root.addNode(newNode) override fun removeNode(refNode: INode<*>) = root.removeNode(refNode) override fun deleteNode(refNode: INode<*>) { refNode.detachFromDocument() eventTree.removeNode(refNode) } override fun removeAll() = root.removeAll() override fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean = root.replaceNode(newNode, refNode) override fun hasChild(refNode: INode<*>): Boolean = root.hasChild(refNode) override fun sequence(): Sequence> = root.sequence() override fun iterator(): Iterator> = root.iterator() override fun getNodeById(id: String): INode<*>? = byId[id]?.let(allNodes::get) override fun getNodesByClassName(className: String): Sequence> = byClass[className]?.asSequence()?.mapNotNull(allNodes::get) ?: emptySequence() private fun addNodeToSearch(node: INode<*>) { allNodes[node.nodeId] = node node.id?.let { byId.putIfAbsent(it, node.nodeId) } for (className in node.classList) { byClass.getOrPut(className, ::mutableSetOf).add(node.nodeId) } } private fun removeNodeFromSearch(node: INode<*>) { allNodes.remove(node.nodeId) node.id?.let { byId.remove(it, node.nodeId) } for (className in node.classList) { val set = byClass.get(className) ?: continue set.remove(node.nodeId) if (set.size == 0) { byClass.remove(className) } } } inline fun on(topLevel: Boolean = false, noinline block: (Event) -> Unit) = if (topLevel) addTopLevelEventListener(EventNames.getEventName(), block) else addEventListener(EventNames.getEventName(), block) override fun > createNode(nodeType: KClass): T = nodeType.java.getConstructor(IDocument::class.java).newInstance(this) }