package ru.dbotthepony.kstarbound.item

import com.google.common.collect.ImmutableSet
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.IStarboundFile
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.item.ItemType
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.JsonPatch
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Future

object ItemRegistry {
	class Entry(
		val name: String,
		val type: ItemType,
		val json: JsonObject,
		val isEmpty: Boolean,
		val friendlyName: String,
		val itemTags: ImmutableSet<String>,
		val itemAgingScripts: ImmutableSet<AssetPath>,
		val file: IStarboundFile?
	) {
		val isNotEmpty: Boolean
			get() = !isEmpty

		val directory = file?.computeDirectory() ?: "/"
	}

	private val LOGGER = LogManager.getLogger()
	private val entries = HashMap<String, Entry>()
	private val tasks = ConcurrentLinkedQueue<Runnable>()

	val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null)

	init {
		entries[""] = AIR
	}

	operator fun get(name: String): Entry {
		return entries[name] ?: AIR
	}

	@JsonFactory
	data class ReadData(
		val itemName: String,
		val shortdescription: String = "...",
		val itemTags: ImmutableSet<String> = ImmutableSet.of(),
		val itemAgingScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
	)

	@JsonFactory
	data class ReadDataObj(
		val shortdescription: String = "...",
		val itemTags: ImmutableSet<String> = ImmutableSet.of(),
		val itemAgingScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
	)

	private fun addObjectItem(obj: Registry.Entry<ObjectDefinition>) {
		if (obj.key in entries) {
			LOGGER.error("Refusing to overwrite item ${obj.key} with generated item for object (old def originate from ${entries[obj.key]!!.file}; new from ${obj.file})")
			return
		}

		val config = obj.jsonObject.deepCopy()

		if ("inventoryIcon" !in config) {
			config["inventoryIcon"] = Globals.itemParameters.missingIcon.fullPath
			LOGGER.warn("inventoryIcon is missing for object item ${obj.key}, using default ${Globals.itemParameters.missingIcon.fullPath}")
		}

		config["itemName"] = obj.key

		if ("tooltipKind" !in config)
			config["tooltipKind"] = "object"

		// this is required for Lua call to "get item config"
		config["printable"] = obj.value.printable

		// Don't inherit object scripts. this is kind of a crappy solution to prevent
		// ObjectItems (which are firable and therefore scripted) from trying to
		// execute scripts intended for objects
		config.remove("scripts")

		val readData = Starbound.gson.fromJson(config, ReadDataObj::class.java)

		entries[obj.key] = Entry(
			name = obj.key,
			type = ItemType.OBJECT,
			json = config,
			isEmpty = false,
			friendlyName = readData.shortdescription,
			itemTags = readData.itemTags,
			itemAgingScripts = readData.itemAgingScripts,
			file = obj.file,
		)
	}

	fun finishLoad() {
		tasks.forEach { it.run() }
		tasks.clear()

		for (obj in Registries.worldObjects.keys.values) {
			if (obj.value.hasObjectItem) {
				addObjectItem(obj)
			}
		}
	}

	fun load(fileTree: Map<String, Collection<IStarboundFile>>, patches: Map<String, Collection<IStarboundFile>>): Collection<Future<*>> {
		val futures = ArrayList<Future<*>>()
		val data by lazy { Starbound.gson.getAdapter(ReadData::class.java) }

		for (type in ItemType.entries) {
			val files = fileTree[type.extension ?: continue] ?: continue

			for (file in files) {
				futures.add(Starbound.EXECUTOR.submit {
					try {
						val read = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(file.jsonReader()), patches[file.computeFullPath()]) as JsonObject
						val readData = data.fromJsonTree(read)

						tasks.add {
							if (readData.itemName in entries) {
								LOGGER.warn("Overwriting item definition at ${readData.itemName} (old def originate from ${entries[readData.itemName]!!.file}; new from $file)")
							}

							entries[readData.itemName] = Entry(
								name = readData.itemName,
								type = type,
								json = read,
								isEmpty = false,
								friendlyName = readData.shortdescription,
								itemTags = readData.itemTags,
								itemAgingScripts = readData.itemAgingScripts,
								file = file,
							)
						}
					} catch (err: Throwable) {
						LOGGER.error("Reading item definition $file", err)
					}
				})
			}
		}

		return futures
	}
}
