package ru.dbotthepony.kstarbound.world.entities.player

import com.google.gson.JsonElement
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.actor.HumanoidData
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.JsonPath
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
import ru.dbotthepony.kstarbound.network.syncher.networkedData
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumExtraStupid
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
import ru.dbotthepony.kstarbound.network.syncher.networkedString
import ru.dbotthepony.kstarbound.world.entities.Animator
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
import ru.dbotthepony.kstarbound.world.entities.StatusController
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.UUID
import kotlin.properties.Delegates

class PlayerEntity() : HumanoidActorEntity() {
	enum class State(override val jsonName: String) : IStringSerializable {
		IDLE("Idle"),
		WALK("Walk"),
		RUN("Run"),
		JUMP("Jump"),
		FALL("Fall"),
		SWIM("Swim"),
		SWIM_IDLE("SwimIdle"),
		TELEPORT_IN("TeleportIn"),
		TELEPORT_OUT("TeleportOut"),
		CROUCH("Crouch"),
		LOUNGE("Lounge");
	}

	constructor(data: DataInputStream, isLegacy: Boolean) : this() {
		uniqueID.accept(KOptional(data.readInternedString()))
		description = data.readInternedString()
		gamemode = PlayerGamemode.entries[if (isLegacy) data.readInt() else data.readUnsignedByte()]
		humanoidData = HumanoidData.read(data, isLegacy)
	}

	override val type: EntityType
		get() = EntityType.PLAYER

	var gamemode = PlayerGamemode.CASUAL

	override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
		uniqueID.get().ifPresent {
			stream.writeBinaryString(it)
		}.ifNotPresent {
			stream.writeBinaryString("")
		}

		stream.writeBinaryString(description)
		if (isLegacy) stream.writeInt(gamemode.ordinal) else stream.writeByte(gamemode.ordinal)
		humanoidData.write(stream, isLegacy)
	}

	val inventory = PlayerInventory()
	val songbook = Songbook(this)
	val effectAnimator = if (Globals.player.effectsAnimator.value == null) Animator() else Animator(Globals.player.effectsAnimator.value!!)
	override val statusController = StatusController(this, Globals.player.statusControllerSettings)
	val techController = TechController(this)

	var state by networkGroup.upstream.add(networkedEnum(State.IDLE))
	var shifting by networkGroup.upstream.add(networkedBoolean())
	private var xAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125))
	private var yAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125).also { it.interpolator = Interpolator.Linear })
	var humanoidData by networkGroup.upstream.add(networkedData(HumanoidData(), HumanoidData.CODEC, HumanoidData.LEGACY_CODEC))

	init {
		networkGroup.upstream.add(team)
	}

	val landed = networkGroup.upstream.add(networkedEventCounter())
	var chatMessage by networkGroup.upstream.add(networkedString())
	val newChatMessage = networkGroup.upstream.add(networkedEventCounter())
	var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE))

	init {
		networkGroup.upstream.add(inventory.networkGroup)
		networkGroup.upstream.add(toolsNetworkGroup)
		networkGroup.upstream.add(armorNetworkGroup)
		networkGroup.upstream.add(songbook.networkGroup)
		networkGroup.upstream.add(movement.networkGroup)
		networkGroup.upstream.add(effects.networkGroup)
		networkGroup.upstream.add(effectAnimator.networkGroup)
		networkGroup.upstream.add(statusController)
		networkGroup.upstream.add(techController.networkGroup)
	}

	override val metaBoundingBox: AABB
		get() = Globals.player.metaBoundBox + position

	override val aimPosition: Vector2d
		get() = Vector2d(xAimPosition, yAimPosition)

	override val isPersistent: Boolean
		get() = false

	var uuid: UUID by Delegates.notNull()
		private set
}
