package ru.dbotthepony.kstarbound.network.syncher

import it.unimi.dsi.fastutil.bytes.ByteArrayList
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import ru.dbotthepony.kommons.io.writeByteArray
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.Objects
import java.util.function.LongSupplier

class MasterElement<E : NetworkedElement>(val upstream: E) : LongSupplier {
	var version: Long = 0L
		private set

	override fun getAsLong(): Long {
		return version
	}

	init {
		upstream.specifyVersioner(this)
	}

	fun write(remoteVersion: Long = 0L, isLegacy: Boolean = false): Pair<ByteArrayList, Long> {
		if (remoteVersion < 0L)
			throw IllegalArgumentException("remote version is negative")
		else if (remoteVersion == 0L) {
			val output = FastByteArrayOutputStream()
			val stream = DataOutputStream(output)
			stream.write(1)
			upstream.writeInitial(stream, isLegacy)
			return ByteArrayList.wrap(output.array, output.length) to ++version
		} else {
			val output = FastByteArrayOutputStream()
			val stream = DataOutputStream(output)

			if (upstream.hasChangedSince(remoteVersion)) {
				stream.write(0)
				upstream.writeDelta(stream, remoteVersion, isLegacy)
				return ByteArrayList.wrap(output.array, output.length) to ++version
			}

			return ByteArrayList() to version
		}
	}

	fun write(stream: OutputStream, remoteVersion: Long = 0L, isLegacy: Boolean = false): Long {
		val (data, version) = write(remoteVersion, isLegacy)
		stream.writeByteArray(data.elements(), 0, data.size)
		return version
	}

	fun read(data: InputStream, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
		if (data.available() == 0) {
			upstream.readBlankDelta(interpolationTime)
		} else {
			val stream = DataInputStream(data)

			if (stream.readBoolean()) {
				upstream.readInitial(stream, isLegacy)
			} else {
				upstream.readDelta(stream, interpolationTime, isLegacy)
			}
		}
	}

	override fun toString(): String {
		return "MasterElement[$upstream]"
	}

	fun read(data: ByteArray, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
		return read(FastByteArrayInputStream(data), interpolationTime, isLegacy)
	}

	fun read(data: ByteArray, offset: Int, length: Int, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
		Objects.checkFromIndexSize(offset, length, data.size)
		return read(FastByteArrayInputStream(data, offset, length), interpolationTime, isLegacy)
	}

	fun read(data: ByteArrayList, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
		return read(data.elements(), 0, data.size, interpolationTime, isLegacy)
	}
}
