package ru.dbotthepony.kstarbound.network.syncher

import java.util.LinkedList

class InterpolationQueue<T>(private val visitor: (T) -> Unit) : Iterable<T> {
	private data class Entry<T>(val time: Double, val value: T) : Comparable<Entry<*>> {
		override fun compareTo(other: Entry<*>): Int {
			return time.compareTo(other.time)
		}
	}

	private var currentTime = 0.0
 	private val queue = LinkedList<Entry<T>>()

	/**
	 * Clears all interpolated values
	 */
	fun drop() {
		queue.clear()
	}

	/**
	 * Applies interpolation queue, and clears it
	 */
	fun clear() {
		try {
			queue.forEach { visitor(it.value) }
		} finally {
			queue.clear()
		}
	}

	override fun iterator(): Iterator<T> {
		return object : Iterator<T> {
			private val parent = queue.iterator()

			override fun hasNext(): Boolean {
				return parent.hasNext()
			}

			override fun next(): T {
				return parent.next().value
			}
		}
	}

	val isEmpty: Boolean
		get() = queue.isEmpty()

	val isNotEmpty: Boolean
		get() = queue.isNotEmpty()

	val size: Int
		get() = queue.size

	fun last(): T {
		return queue.last.value
	}

	inline fun last(orElse: () -> T): T {
		if (isEmpty)
			return orElse.invoke()
		else
			return last()
	}

	fun tick(delta: Double) {
		require(delta >= 0.0) { "Negative interpolation delta: $delta" }
		currentTime += delta

		while (queue.isNotEmpty() && queue[0].time <= currentTime) {
			visitor(queue.removeAt(0).value)
		}
	}

	fun push(value: T, delay: Double, visitAllIfDeviated: Boolean = true) {
		val actualTime = delay + currentTime

		// Only append an incoming delta to our pending value list if the incoming
		// step is forward in time of every other pending value.  In any other
		// case, this is an error or the step tracking is wildly off, so just clear
		// any other incoming values.
		if (queue.isNotEmpty() && queue.last.time > actualTime) {
			if (visitAllIfDeviated)
				queue.forEach { visitor(it.value) }

			queue.clear()
		}

		if (delay > 0L)
			queue.add(Entry(actualTime, value))
		else
			visitor(value)
	}
}
