package ru.dbotthepony.kstarbound.network.packets

import it.unimi.dsi.fastutil.bytes.ByteArrayList
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readByteKey
import ru.dbotthepony.kommons.io.readCollection
import ru.dbotthepony.kommons.io.readKOptional
import ru.dbotthepony.kommons.io.readMap
import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeCollection
import ru.dbotthepony.kommons.io.writeKOptional
import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kommons.io.writeVarInt
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.JsonRPC
import ru.dbotthepony.kstarbound.server.ServerConnection
import java.io.DataInputStream
import java.io.DataOutputStream

/**
 * Embeds RPC (remote procedure call) data for setting various variables on other side
 */
class ClientContextUpdatePacket(
	val rpcEntries: List<JsonRPC.Entry>,
	val shipChunks: KOptional<Map<ByteKey, KOptional<ByteArray>>>,
	val networkedVars: KOptional<ByteArrayList>
) : IClientPacket, IServerPacket {
	override fun write(stream: DataOutputStream, isLegacy: Boolean) {
		if (isLegacy) {
			if (!shipChunks.isPresent && !networkedVars.isPresent) {
				// client to server
				stream.writeCollection(rpcEntries) { it.write(this, true) }
			} else {
				// server to client
				// this is so dumb
				val wrap2 = FastByteArrayOutputStream()

				run {
					val wrap = FastByteArrayOutputStream()
					DataOutputStream(wrap).writeCollection(rpcEntries) { it.write(this, true) }
					wrap2.writeByteArray(wrap.array, 0, wrap.length)
				}

				shipChunks.ifPresent {
					val wrap = FastByteArrayOutputStream()
					DataOutputStream(wrap).writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
					wrap2.writeByteArray(wrap.array, 0, wrap.length)
				}

				networkedVars.ifPresent {
					wrap2.writeByteArray(it.elements(), 0, it.size)
				}

				stream.writeByteArray(wrap2.array, 0, wrap2.length)
			}
		} else {
			stream.writeCollection(rpcEntries) { it.write(this, false) }

			stream.writeKOptional(shipChunks) {
				writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
			}

			stream.writeKOptional(networkedVars) {
				writeVarInt(it.size)
				write(it.elements(), 0, it.size)
			}
		}
	}

	override fun play(connection: ServerConnection) {
		connection.rpc.read(rpcEntries)
	}

	override fun play(connection: ClientConnection) {
		connection.rpc.read(rpcEntries)
	}

	companion object {
		fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): ClientContextUpdatePacket {
			if (isLegacy) {
				// beyond stupid
				if (side == ConnectionSide.SERVER) {
					return ClientContextUpdatePacket(
						DataInputStream(FastByteArrayInputStream(stream.readByteArray())).readCollection { JsonRPC.Entry.legacy(this) },
						KOptional(),
						KOptional()
					)
				} else {
					val wrap = DataInputStream(FastByteArrayInputStream(stream.readByteArray()))
					val rpc = wrap.readByteArray()
					val shipChunks = wrap.readByteArray()
					val networkedVars = wrap.readByteArray()

					return ClientContextUpdatePacket(
						if (rpc.isEmpty()) listOf() else DataInputStream(FastByteArrayInputStream(rpc)).readCollection { JsonRPC.Entry.legacy(this) },
						KOptional(if (shipChunks.isEmpty()) mapOf() else DataInputStream(FastByteArrayInputStream(shipChunks)).readMap({ readByteKey() }, { readKOptional { readByteArray() } })),
						KOptional(ByteArrayList.wrap(networkedVars)),
					)
				}
			} else {
				return ClientContextUpdatePacket(
					stream.readCollection { JsonRPC.Entry.native(this) },
					stream.readKOptional { readMap({ readByteKey() }, { readKOptional { readByteArray() } }) },
					stream.readKOptional { ByteArrayList.wrap(readByteArray()) })
			}
		}
	}
}
