package ru.dbotthepony.kstarbound.item

import com.google.gson.JsonNull
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.io.koptional
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
import ru.dbotthepony.kstarbound.network.syncher.JsonElementCodec
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
import ru.dbotthepony.kstarbound.network.syncher.NetworkedList
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
import ru.dbotthepony.kstarbound.network.syncher.NetworkedStatefulItemStack
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
import ru.dbotthepony.kstarbound.network.syncher.networkedData
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint2
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.entities.Animator
import ru.dbotthepony.kstarbound.world.physics.Poly

class ActiveItemStack(entry: ItemRegistry.Entry, config: JsonObject, parameters: JsonObject, size: Long) : ItemStack(entry, config, parameters, size), NetworkedStatefulItemStack.Stateful {
	override val networkElement = NetworkedGroup()

	val animator: Animator

	init {
		val animationPath = AssetPathStack.relativeTo(entry.directory, lookupProperty("animation").asString)
		var animationConfig = Starbound.loadJsonAsset(animationPath) ?: JsonNull.INSTANCE
		val animationCustom = lookupProperty("animationCustom")

		if (!animationCustom.isJsonNull) {
			animationConfig = mergeJson(animationConfig.deepCopy(), animationCustom)
		}

		try {
			if (animationCustom.isJsonNull) {
				animator = Animator()
			} else {
				animator = Animator(Starbound.gson.fromJson(animationConfig, AnimationDefinition::class.java))
			}
		} catch (err: Throwable) {
			throw RuntimeException("Unable to instance animator for ${entry.name} (animation config: $animationPath)", err)
		}

		networkElement.add(animator.networkGroup)
	}

	var holdingItem by networkedBoolean(true).also { networkElement.add(it) }
	var backArmFrame by networkedData(KOptional(), InternedStringCodec.koptional()).also { networkElement.add(it) }
	var frontArmFrame by networkedData(KOptional(), InternedStringCodec.koptional()).also { networkElement.add(it) }
	var twoHandedGrip by networkedBoolean().also { networkElement.add(it) }
	var recoil by networkedBoolean().also { networkElement.add(it) }
	var outsideOfHand by networkedBoolean().also { networkElement.add(it) }
	var armAngle by networkedFixedPoint2(0.01).also { networkElement.add(it) }
	var facingDirection by networkedData(KOptional(), Direction.CODEC.koptional()).also { networkElement.add(it) }

	val damageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
	val itemDamageSources = NetworkedList(DamageSource.CODEC, DamageSource.LEGACY_CODEC).also { networkElement.add(it) }
	val shieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
	val itemShieldPolys = NetworkedList(Poly.CODEC, Poly.LEGACY_CODEC).also { networkElement.add(it) }
	val forceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }
	val itemForceRegions = NetworkedList(PhysicsForceRegion.CODEC, PhysicsForceRegion.LEGACY_CODEC).also { networkElement.add(it) }

	val scriptedAnimationParameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkElement.add(it, false) }

	init {
		for ((k, v) in lookupProperty("animationParts", JsonObject()).asJsonObject.entrySet()) {
			animator.setPartTag(k, "partImage", v.asString)
		}

		val scriptedAnimationParameters = lookupProperty("scriptedAnimationParameters")

		if (scriptedAnimationParameters is JsonObject) {
			for ((k, v) in scriptedAnimationParameters.entrySet()) {
				this.scriptedAnimationParameters[k] = v.deepCopy()
			}
		}
	}

	override fun copy(size: Long): ItemStack {
		if (isEmpty)
			return EMPTY

		return ActiveItemStack(entry, config, parameters.deepCopy(), size)
	}
}
