package ru.dbotthepony.kstarbound.lua.bindings

import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.util.valueOfOrNull
import ru.dbotthepony.kstarbound.world.entities.AnchorNetworkState
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity

fun provideNPCBindings(self: NPCEntity, lua: LuaEnvironment) {
	val callbacks = lua.newTable()
	lua.globals["npc"] = callbacks

	callbacks["toAbsolutePosition"] = luaFunction { pos: Table ->
		returnBuffer.setTo(from(self.toAbsolutePosition(toVector2d(pos))))
	}

	callbacks["species"] = luaFunction { returnBuffer.setTo(self.variant.species.key.toByteString()) }
	callbacks["gender"] = luaFunction { returnBuffer.setTo(self.variant.humanoidIdentity.gender.jsonName.toByteString()) }
	callbacks["humanoidIdentity"] = luaFunction { returnBuffer.setTo(from(Starbound.gson.toJsonTree(self.variant.humanoidIdentity))) }
	callbacks["npcType"] = luaFunction { returnBuffer.setTo(self.variant.typeName.toByteString()) }
	callbacks["seed"] = luaFunction { returnBuffer.setTo(self.variant.seed) }
	callbacks["level"] = luaFunction { returnBuffer.setTo(self.variant.level) }
	callbacks["dropPools"] = luaFunction { returnBuffer.setTo(tableOf(*self.dropPools.map { it.key.left().toByteString() }.toTypedArray())) }

	callbacks["setDropPools"] = luaFunction { dropPools: Table? ->
		self.dropPools.clear()

		if (dropPools != null) {
			for ((_, pool) in dropPools) {
				self.dropPools.add(Registries.treasurePools.ref(pool.toString()))
			}
		}
	}

	// lol why
	callbacks["energy"] = luaFunction { returnBuffer.setTo(self.energy) }
	callbacks["maxEnergy"] = luaFunction { returnBuffer.setTo(self.maxEnergy) }

	callbacks["say"] = luaFunction { line: ByteString, tags: Table?, config: Any? ->
		val actualLine = if (tags != null) {
			SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() })
		} else {
			line.decode()
		}

		val isNotEmpty = actualLine.isNotEmpty()

		if (isNotEmpty)
			self.addChatMessage(actualLine, toJsonFromLua(config))

		returnBuffer.setTo(isNotEmpty)
	}

	callbacks["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table?, config: Any? ->
		val actualLine = if (tags != null) {
			SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() })
		} else {
			line.decode()
		}

		val isNotEmpty = actualLine.isNotEmpty()

		if (isNotEmpty)
			self.addChatMessage(actualLine, toJsonFromLua(config), portrait.decode())

		returnBuffer.setTo(isNotEmpty)
	}

	callbacks["emote"] = luaFunction { emote: ByteString ->
		self.addEmote(emote.decode())
	}

	callbacks["dance"] = luaFunction { dance: ByteString ->
		self.setDance(dance.decode())
	}

	callbacks["setInteractive"] = luaFunction { isInteractive: Boolean ->
		self.isInteractive = isInteractive
	}

	callbacks["setLounging"] = luaFunction { loungeable: Number, oAnchorIndex: Number? ->
		val anchorIndex = oAnchorIndex?.toInt() ?: 0
		val entity = self.world.entities[loungeable.toInt()] as? LoungeableEntity

		if (entity == null || anchorIndex !in 0 until entity.anchors.size || entity.entitiesLoungingIn(anchorIndex).isNotEmpty()) {
			returnBuffer.setTo(false)
		} else {
			self.movement.anchorNetworkState = AnchorNetworkState(loungeable.toInt(), anchorIndex)
			returnBuffer.setTo(true)
		}
	}

	callbacks["resetLounging"] = luaFunction {
		self.movement.anchorNetworkState = null
	}

	callbacks["isLounging"] = luaFunction {
		returnBuffer.setTo(self.movement.anchorNetworkState != null)
	}

	callbacks["loungingIn"] = luaFunction {
		returnBuffer.setTo(self.movement.anchorNetworkState?.entityID)
	}

	callbacks["setOfferedQuests"] = luaFunction { values: Table? ->
		self.offeredQuests.clear()

		if (values != null) {
			for ((_, quest) in values) {
				self.offeredQuests.add(Starbound.gson.fromJsonFast(toJsonFromLua(quest), QuestArcDescriptor::class.java))
			}
		}
	}

	callbacks["setTurnInQuests"] = luaFunction { values: Table? ->
		self.turnInQuests.clear()

		if (values != null) {
			for ((_, value) in values) {
				self.turnInQuests.add(value.toString())
			}
		}
	}

	callbacks["setItemSlot"] = luaFunction { slot: ByteString, descriptor: Any ->
		returnBuffer.setTo(self.setItem(slot.decode(), ItemDescriptor(descriptor)))
	}

	callbacks["getItemSlot"] = luaFunction { slot: ByteString ->
		val decoded = slot.decode()
		val slotType = HumanoidActorEntity.ItemSlot.entries.valueOfOrNull(decoded.lowercase())

		if (slotType == null) {
			if (decoded in self.variant.items) {
				returnBuffer.setTo(self.variant.items[decoded]!!.toTable(this))
			} else {
				returnBuffer.setTo()
			}
		} else {
			returnBuffer.setTo(self.getItem(slotType).toTable(this))
		}
	}

	callbacks["disableWornArmor"] = luaFunction { disable: Boolean ->
		self.disableWornArmor = disable
	}

	callbacks["beginPrimaryFire"] = luaFunction { self.beginPrimaryFire() }
	callbacks["beginAltFire"] = luaFunction { self.beginSecondaryFire() }
	callbacks["beginSecondaryFire"] = luaFunction { self.beginSecondaryFire() }
	callbacks["endPrimaryFire"] = luaFunction { self.endPrimaryFire() }
	callbacks["endAltFire"] = luaFunction { self.endSecondaryFire() }
	callbacks["endSecondaryFire"] = luaFunction { self.endSecondaryFire() }

	callbacks["setShifting"] = luaFunction { value: Boolean ->
		self.isShifting = value
	}

	callbacks["setDamageOnTouch"] = luaFunction { value: Boolean ->
		self.damageOnTouch = value
	}

	callbacks["aimPosition"] = luaFunction {
		returnBuffer.setTo(from(self.aimPosition))
	}

	callbacks["setAimPosition"] = luaFunction { pos: Table ->
		self.aimPosition = self.world.geometry.diff(toVector2d(pos), self.position)
	}

	callbacks["setDeathParticleBurst"] = luaFunction { value: ByteString? ->
		self.deathParticleBurst = value?.decode()?.sbIntern()
	}

	callbacks["setStatusText"] = luaFunction { value: ByteString? ->
		self.statusText = value?.decode()?.sbIntern()
	}

	callbacks["setDisplayNametag"] = luaFunction { value: Boolean ->
		self.displayNametag = value
	}

	callbacks["setPersistent"] = luaFunction { value: Boolean ->
		self.isPersistent = value
	}

	callbacks["setKeepAlive"] = luaFunction { value: Boolean ->
		self.keepAlive = value
	}

	callbacks["setAggressive"] = luaFunction { value: Boolean ->
		self.isAggressive = value
	}

	callbacks["setDamageTeam"] = luaFunction { value: Table ->
		self.team.accept(Starbound.gson.fromJsonFast(toJsonFromLua(value), EntityDamageTeam::class.java))
	}

	callbacks["setUniqueId"] = luaFunction { value: ByteString? ->
		self.uniqueID.accept(value?.decode()?.sbIntern())
	}
}