package ru.dbotthepony.kstarbound.network.packets

import com.google.gson.JsonArray
import ru.dbotthepony.kommons.io.readUUID
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.readJsonArray
import ru.dbotthepony.kstarbound.json.writeJsonArray
import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.server.ServerConnection
import ru.dbotthepony.kstarbound.world.World
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.*
import java.util.function.Consumer
import java.util.function.Function

class EntityMessagePacket(val entity: Either<Int, String>, val message: String, val arguments: JsonArray, val id: UUID, val sourceConnection: Int) : IClientPacket, IServerPacket {
	constructor(stream: DataInputStream, isLegacy: Boolean) : this(
		if (stream.readBoolean()) Either.right(stream.readInternedString()) else Either.left(stream.readInt()),
		stream.readInternedString(),
		stream.readJsonArray(),
		stream.readUUID(),
		stream.readUnsignedShort()
	)

	override fun write(stream: DataOutputStream, isLegacy: Boolean) {
		stream.writeBoolean(entity.isRight)

		if (entity.isLeft) {
			stream.writeInt(entity.left())
		} else {
			stream.writeBinaryString(entity.right())
		}

		stream.writeBinaryString(message)
		stream.writeJsonArray(arguments)
		stream.writeUUID(id)
		stream.writeShort(sourceConnection)
	}

	override fun play(connection: ServerConnection) {
		val tracker = connection.tracker

		if (tracker == null) {
			connection.send(EntityMessageResponsePacket(Either.left("Not in world"), this@EntityMessagePacket.id))
		} else {
			tracker.handleEntityMessage(this)
		}
	}

	override fun play(connection: ClientConnection) {
		connection.enqueue {
			val world = world

			if (world != null) {
				val future = if (entity.isLeft) {
					world.dispatchEntityMessage(connection.connectionID, entity.left(), message, arguments)
				} else {
					world.dispatchEntityMessage(connection.connectionID, entity.right(), message, arguments)
				}

				future
					.thenAccept(Consumer {
						connection.send(EntityMessageResponsePacket(Either.right(it), this@EntityMessagePacket.id))
					})
					.exceptionally(Function {
						connection.send(EntityMessageResponsePacket(Either.left(it.message ?: "Internal server error"), this@EntityMessagePacket.id))
						null
					})
			}
		}
	}
}
