package ru.dbotthepony.kstarbound.network.packets.serverbound

import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readByteKey
import ru.dbotthepony.kommons.io.readKOptional
import ru.dbotthepony.kommons.io.readMap
import ru.dbotthepony.kommons.io.readUUID
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeByteKey
import ru.dbotthepony.kommons.io.writeKOptional
import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
import ru.dbotthepony.kstarbound.server.ServerConnection
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.InputStream
import java.util.*
import kotlin.collections.HashMap

data class ClientConnectPacket(
	val assetDigest: ByteArray, val allowAssetMismatch: Boolean,
	val playerUuid: UUID, val playerName: String, val playerSpecies: String,
	val shipChunks: Map<ByteKey, KOptional<ByteArray>>, val shipUpgrades: ShipUpgrades, val introComplete: Boolean,
	val account: String
) : IServerPacket {
	constructor(stream: DataInputStream, isLegacy: Boolean) : this(
		stream.readByteArray(),
		stream.readBoolean(),
		stream.readUUID(),
		stream.readBinaryString(),
		stream.readBinaryString(),
		stream.readMap(InputStream::readByteKey, { readKOptional { readByteArray() } }, { HashMap(it) }),
		ShipUpgrades(stream, isLegacy),
		stream.readBoolean(),
		stream.readBinaryString()
	)

	override fun write(stream: DataOutputStream, isLegacy: Boolean) {
		stream.writeByteArray(assetDigest)
		stream.writeBoolean(allowAssetMismatch)
		stream.writeUUID(playerUuid)
		stream.writeBinaryString(playerName)
		stream.writeBinaryString(playerSpecies)
		stream.writeMap(shipChunks, { writeByteKey(it) }, { writeKOptional(it) { writeByteArray(it) } })
		shipUpgrades.write(stream, isLegacy)
		stream.writeBoolean(introComplete)
		stream.writeBinaryString(account)
	}

	override fun play(connection: ServerConnection) {
		if (connection.server.clientByUUID(playerUuid) != null) {
			connection.send(ConnectFailurePacket("Duplicate player UUID $playerUuid"))
			LOGGER.warn("Unable to accept player $playerName/$playerUuid because such UUID is already taken")
			connection.channel.flush()
			connection.close()
		} else {
			LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')")

			connection.nickname = connection.server.reserveNickname(playerName, "Player_${connection.connectionID}")
			connection.shipUpgrades = shipUpgrades
			connection.uuid = playerUuid

			connection.receiveShipChunks(shipChunks)
			connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation))
			connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.time))
			connection.channel.flush()
			connection.inGame()
		}
	}

	companion object {
		const val DIGEST_SIZE = 32
		private val LOGGER = LogManager.getLogger()
	}
}
