package ru.dbotthepony.kstarbound.network

import io.netty.buffer.ByteBuf
import it.unimi.dsi.fastutil.bytes.ByteArrayList
import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.server.ServerConnection
import java.io.DataInputStream
import java.io.DataOutputStream
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf

fun ByteBuf.writeUTF(value: String) {
	writeBytes(value.toByteArray().also { check(!it.any { it.toInt() == 0 }) { "Provided UTF string contains NUL" } })
	writeByte(0)
}

fun ByteBuf.readUTF(): String {
	val bytes = ByteArrayList()
	var read = readByte()

	while (read.toInt() != 0) {
		bytes.add(read)
		read = readByte()
	}

	return String(bytes.toByteArray())
}

enum class ConnectionSide {
	SERVER,
	CLIENT;

	val opposite: ConnectionSide
		get() = if (this == SERVER) CLIENT else SERVER
}

enum class ConnectionState {
	FRESH,
	WORKING,
	CLOSED;
}

enum class PacketDirection(val acceptOnClient: Boolean, val acceptOnServer: Boolean) {
	FROM_SERVER(true, false),
	FROM_CLIENT(false, true),
	BI_DIRECTIONAL(true, true);

	fun acceptedOn(side: ConnectionSide): Boolean {
		if (side == ConnectionSide.SERVER)
			return acceptOnServer

		return acceptOnClient
	}

	companion object {
		fun get(type: KClass<out IPacket>): PacketDirection {
			return of(type.isSubclassOf(IClientPacket::class), type.isSubclassOf(IServerPacket::class))
		}

		fun of(allowedOnClient: Boolean, allowedOnServer: Boolean): PacketDirection {
			if (allowedOnServer && allowedOnClient)
				return BI_DIRECTIONAL
			else if (allowedOnServer)
				return FROM_CLIENT
			else if (allowedOnClient)
				return FROM_SERVER
			else
				throw IllegalArgumentException("Packet is not allowed on either side")
		}
	}
}

enum class ConnectionType {
	NETWORK,
	MEMORY;
}

fun interface IPacketReaderDetailed<T : IPacket> {
	fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): T
}

fun interface IPacketReader<T : IPacket> : IPacketReaderDetailed<T> {
	fun read(stream: DataInputStream, isLegacy: Boolean): T

	override fun read(stream: DataInputStream, isLegacy: Boolean, side: ConnectionSide): T {
		return read(stream, isLegacy)
	}
}

interface IPacket {
	fun write(stream: DataOutputStream, isLegacy: Boolean)
}

interface IServerPacket : IPacket {
	fun play(connection: ServerConnection)
}

interface IClientPacket : IPacket {
	fun play(connection: ClientConnection)
}
