package ru.dbotthepony.kstarbound.network.syncher

import ru.dbotthepony.kommons.io.StreamCodec
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.kommons.util.ListenableDelegate
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.*
import java.util.function.Consumer

open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protected val codec: StreamCodec<TYPE>, protected val legacyCodec: StreamCodec<LEGACY>, protected val toLegacy: (TYPE) -> LEGACY, protected val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
	protected val valueListeners = Listenable.Impl<TYPE>()
	protected val queue = InterpolationQueue<TYPE> { this.value = it; valueListeners.accept(value) }
	protected var isInterpolating = false
	protected var currentTime = 0.0

	override fun toString(): String {
		return "BasicNetworkedElement[$value, isInterpolating=$isInterpolating, currentTime=$currentTime]"
	}

	override fun accept(t: TYPE) {
		if (t != value) {
			value = t
			queue.clear()
			bumpVersion()
			valueListeners.accept(t)
		}
	}

	override fun addListener(listener: Consumer<TYPE>): Listenable.L {
		return valueListeners.addListener(listener)
	}

	override fun get(): TYPE {
		return value
	}

	override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
		val old = value
		value = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
		queue.clear()
		bumpVersion()

		if (value != old) {
			valueListeners.accept(value)
		}
	}

	override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
		if (isLegacy) {
			legacyCodec.write(data, toLegacy(queue.last { value }))
		} else {
			codec.write(data, queue.last { value })
		}
	}

	override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
		val read = if (isLegacy) fromLegacy(legacyCodec.read(data)) else codec.read(data)
		bumpVersion()

		if (isInterpolating) {
			queue.push(read, interpolationDelay)
		} else {
			value = read
			valueListeners.accept(read)
		}
	}

	override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
		writeInitial(data, isLegacy)
	}

	override fun readBlankDelta(interpolationDelay: Double) {}

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

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

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