package ru.dbotthepony.kstarbound.network.syncher

import ru.dbotthepony.kommons.io.StreamCodec
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.io.writeVarInt
import ru.dbotthepony.kommons.util.KOptional
import java.io.DataInputStream
import java.io.DataOutputStream

/**
 * NetElement that sends signals during delta writes that can be received by
 * slaves.  It has no 'state', and nothing is sent during a store / load, and
 * it only keeps past signals for a maximum number of versions.  Thus, it is
 * not appropriate to use to send updates to long term states, only for event
 * like things that are not harmful if missed.
 */
class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize: Int = 100) : NetworkedElement(), Iterable<S>, Iterator<S> {
	private data class Signal<S>(val version: Long, val signal: S)

	private val visibleSignals = ArrayDeque<Signal<S>>()
	private val internalSignals = ArrayDeque<Signal<S>>()
	private val delayedSignals = InterpolationQueue<S> { push(it) }
	private var isInterpolating = false

	override fun readInitial(data: DataInputStream, isLegacy: Boolean) {}
	override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {}
	override fun readBlankDelta(interpolationDelay: Double) {}

	override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
		val toRead = data.readVarInt()

		for (i in 0 until toRead) {
			val signal = codec.read(data)

			if (isInterpolating && interpolationDelay > 0.0) {
				delayedSignals.push(signal, interpolationDelay)
			} else {
				push(signal)
			}
		}
	}

	override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
		data.writeVarInt(internalSignals.count { it.version >= remoteVersion })

		for ((version, signal) in internalSignals) {
			if (version >= remoteVersion) {
				codec.write(data, signal)
			}
		}
	}

	override fun hasChangedSince(version: Long): Boolean {
		return internalSignals.any { it.version >= version }
	}

	override fun enableInterpolation(extrapolation: Double) {
		isInterpolating = true
	}

	override fun disableInterpolation() {
		isInterpolating = false
		delayedSignals.clear()
	}

	override fun tickInterpolation(delta: Double) {
		delayedSignals.tick(delta)
	}

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

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

	fun push(signal: S) {
		val value = Signal(currentVersion(), signal)
		visibleSignals.add(value)
		internalSignals.add(value)

		while (visibleSignals.size >= maxSize) {
			visibleSignals.removeFirst()
		}

		while (internalSignals.size >= maxSize) {
			internalSignals.removeFirst()
		}
	}

	fun poll(): KOptional<S> {
		if (visibleSignals.isEmpty())
			return KOptional()

		return KOptional(visibleSignals.removeFirst().signal)
	}

	override fun iterator(): Iterator<S> {
		return this
	}

	override fun hasNext(): Boolean {
		return visibleSignals.isNotEmpty()
	}

	override fun next(): S {
		return visibleSignals.removeFirst().signal
	}
}
