package ru.dbotthepony.mc.otm.graph

import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
import ru.dbotthepony.mc.otm.core.util.ITickable
import java.lang.ref.WeakReference
import java.util.*
import kotlin.collections.ArrayList

open class Abstract6Graph<N : Graph6Node<N, G>, G : Abstract6Graph<N, G>> : IConditionalTickable {
	private val nodesInternal = ArrayList<N>()
	private val conditional = ArrayList<IConditionalTickable>()
	private val always = ArrayList<ITickable>()

	protected val nodes: List<N> = Collections.unmodifiableList(nodesInternal)

	val size get() = nodesInternal.size

	var isMerged = false
		private set

	private var isTicking = false

	/**
	 * Allows storing arbitrary data by external code
	 */
	val userData = HashMap<UUID, Any>()

	open fun onNodeRemoved(node: N) {}
	open fun onNodeAdded(node: N) {}
	open fun onMergedInto(other: G) {}

	protected open fun innerTick(): Boolean {
		return false
	}

	final override fun tick(): Boolean {
		if (isMerged)
			return false

		// позволяет вершинам изменять список тикающих вершин
		for (i in conditional.size - 1 downTo 0) {
			val node = conditional[i]

			if (!node.tick()) {
				conditional.removeAt(i)
			}
		}

		// позволяет вершинам изменять список тикающих вершин
		for (node in always.size - 1 downTo 0) {
			always[node].tick()
		}

		return innerTick() || always.isNotEmpty() || conditional.isNotEmpty()
	}

	fun addNode(node: N): Boolean {
		if (node in nodesInternal)
			return false

		nodesInternal.add(node)

		if (node is IConditionalTickable) {
			conditional.add(node)

			if (!isTicking) {
				isTicking = true
				next.add(WeakReference(this))
			}
		} else if (node is ITickable) {
			always.add(node)

			if (!isTicking) {
				isTicking = true
				next.add(WeakReference(this))
			}
		}

		onNodeAdded(node)
		return true
	}

	fun removeNode(node: N): Boolean {
		if (!nodesInternal.remove(node))
			return false

		nodesInternal.remove(node)

		if (node is IConditionalTickable)
			conditional.remove(node)
		else if (node is ITickable)
			always.remove(node)

		onNodeRemoved(node)
		return true
	}

	fun retain(nodes: Set<N>) {
		for (i in this.nodesInternal.size - 1 downTo 0) {
			if (this.nodesInternal[i] !in nodes) {
				val node = this.nodesInternal[i]

				this.nodesInternal.removeAt(i)

				if (node is IConditionalTickable)
					conditional.remove(node)
				else if (node is ITickable)
					always.remove(node)

				onNodeRemoved(node)
			}
		}
	}

	fun merge(other: G, setter: (N, G) -> Unit): G {
		if (other === this)
			return this

		if (size >= other.size) {
			for (node in other.nodesInternal) {
				nodesInternal.add(node)
				setter.invoke(node, this as G)
				onNodeAdded(node)

				if (node is IConditionalTickable) {
					conditional.add(node)

					if (!isTicking) {
						isTicking = true
						next.add(WeakReference(this))
					}
				} else if (node is ITickable) {
					always.add(node)

					if (!isTicking) {
						isTicking = true
						next.add(WeakReference(this))
					}
				}
			}

			other.isMerged = true
			other.onMergedInto(this as G)
			return this
		} else {
			return other.merge(this as G, setter)
		}
	}

	companion object {
		private val graphs = ArrayList<WeakReference<Abstract6Graph<*, *>>>()
		private val next = ArrayList<WeakReference<Abstract6Graph<*, *>>>()

		fun tick() {
			if (next.isNotEmpty()) {
				graphs.addAll(next)
				next.clear()
			}

			val iterator = graphs.iterator()

			for (value in iterator) {
				val graph = value.get()

				if (graph == null || !graph.tick()) {
					graph?.isTicking = false
					iterator.remove()
				}
			}
		}
	}
}
