package ru.dbotthepony.kstarbound.network.syncher

import ru.dbotthepony.kstarbound.item.ItemStack
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.function.LongSupplier

class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : NetworkedItemStack(value) {
	interface Stateful {
		val networkElement: NetworkedElement
	}

	private var isInterpolating = false
	private var extrapolation = 0.0

	override fun enableInterpolation(extrapolation: Double) {
		isInterpolating = true
		this.extrapolation = extrapolation
		(itemStack as? Stateful)?.networkElement?.enableInterpolation(extrapolation)
	}

	override fun disableInterpolation() {
		isInterpolating = false
		(itemStack as? Stateful)?.networkElement?.disableInterpolation()
	}

	override fun specifyVersioner(versionCounter: LongSupplier) {
		super.specifyVersioner(versionCounter)
		(itemStack as? Stateful)?.networkElement?.disableInterpolation()
	}

	override fun hasChangedSince(version: Long): Boolean {
		return super.hasChangedSince(version) || (get() as? Stateful)?.networkElement?.hasChangedSince(version) == true
	}

	override fun accept(t: ItemStack) {
		super.accept(t)

		if (t is Stateful) {
			if (versionCounter != null) {
				t.networkElement.specifyVersioner(versionCounter!!)
			}

			if (isInterpolating) {
				t.networkElement.enableInterpolation(extrapolation)
			} else {
				t.networkElement.disableInterpolation()
			}
		}
	}

	override fun readBlankDelta(interpolationDelay: Double) {
		(itemStack as? Stateful)?.networkElement?.readBlankDelta(interpolationDelay)
	}

	override fun tickInterpolation(delta: Double) {
		(itemStack as? Stateful)?.networkElement?.tickInterpolation(delta)
	}

	override fun toString(): String {
		return "NetworkedStatefulItemStack[$itemStack]"
	}

	override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
		super.readInitial(data, isLegacy)

		val stack = itemStack

		if (stack is Stateful) {
			val versionCounter = versionCounter

			if (versionCounter != null)
				stack.networkElement.specifyVersioner(versionCounter)

			if (isInterpolating)
				stack.networkElement.enableInterpolation(extrapolation)

			stack.networkElement.readInitial(data, isLegacy)
		}
	}

	override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
		super.writeInitial(data, isLegacy)

		val stack = itemStack

		if (stack is Stateful) {
			stack.networkElement.writeInitial(data, isLegacy)
		}
	}

	override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
		while (true) {
			when (val type = data.readUnsignedByte()) {
				0 -> break

				1 -> {
					super.readInitial(data, isLegacy)

					val stack = itemStack

					if (stack is Stateful) {
						val versionCounter = versionCounter

						if (versionCounter != null) {
							stack.networkElement.specifyVersioner(versionCounter)
						}

						if (isInterpolating) {
							stack.networkElement.enableInterpolation(extrapolation)
						}
					}
				}

				2 -> {
					val stack = itemStack

					if (stack is Stateful) {
						stack.networkElement.readInitial(data, isLegacy)
					} else {
						throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
					}
				}

				3 -> {
					val stack = itemStack

					if (stack is Stateful) {
						stack.networkElement.readDelta(data, interpolationDelay, isLegacy)
					} else {
						throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
					}
				}

				else -> throw IllegalArgumentException("Unknown change type $type")
			}
		}
	}

	override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
		if (super.hasChangedSince(remoteVersion)) {
			data.writeByte(1)
			super.writeInitial(data, isLegacy)

			val stack = itemStack

			if (stack is Stateful) {
				data.writeByte(2)
				stack.networkElement.writeInitial(data, isLegacy)
			}
		} else {
			val stack = itemStack

			if (stack is Stateful && stack.networkElement.hasChangedSince(remoteVersion)) {
				data.writeByte(3)
				stack.networkElement.writeDelta(data, remoteVersion, isLegacy)
			}
		}

		data.writeByte(0)
	}
}
