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.concurrent.CopyOnWriteArrayList
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 = CopyOnWriteArrayList<Listener>()

	protected val queue = InterpolationQueue<TYPE> {
		val old = this.value
		this.value = it
		valueListeners.forEach { c -> c.callable.invoke(it, old) }
	}

	protected var isInterpolating = false
	protected var currentTime = 0.0

	protected inner class Listener(val callable: (TYPE, TYPE) -> Unit) : Listenable.L {
		constructor(listener: Consumer<TYPE>) : this({ v, _ -> listener.accept(v) })
		constructor(listener: Runnable) : this({ _, _ -> listener.run() })

		init {
			valueListeners.add(this)
		}

		override fun remove() {
			valueListeners.remove(this)
		}
	}

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

	override fun accept(t: TYPE) {
		if (t != value) {
			val old = value
			value = t
			queue.clear()
			bumpVersion()
			valueListeners.forEach { it.callable.invoke(t, old) }
		}
	}

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

	fun addListener(listener: (new: TYPE, old: TYPE) -> Unit): Listenable.L {
		return Listener(listener)
	}

	@Deprecated("Internal API")
	override fun listen(listener: Runnable): Listenable.L {
		return Listener(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.forEach { it.callable.invoke(value, old) }
		}
	}

	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 {
			val old = value
			value = read
			valueListeners.forEach { it.callable.invoke(read, old) }
		}
	}

	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)
	}
}
