package ru.dbotthepony.kstarbound.defs.dungeon

import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.world.Direction

enum class DungeonBrushType(override val jsonName: String) : IStringSerializable {
	INVALID("invalid") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.Invalid
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("invalid" in json) {
				return DungeonBrush.Invalid
			}

			return null
		}
	},

	CLEAR("clear") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.Clear
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if (json["clear"]?.asString == "true") {
				return DungeonBrush.Clear
			}

			return null
		}
	},

	RANDOM("random") {
		private val adapter by lazy {
			Starbound.gson.getAdapter(object : TypeToken<ImmutableList<DungeonBrush>>() {})
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.Random(adapter.fromJsonTree(json[1]))
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			return null
		}
	},

	FOREGROUND("front") {
		private val adapter by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Tile.JsonData::class.java)
		}

		private val adapterTiled by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Tile.TiledData::class.java)
		}

		private val adapter0 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<TileDefinition>>() {})
		}

		private val adapter1 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<TileModifierDefinition>>() {})
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			if (json.size() == 3) {
				return DungeonBrush.Tile(false, adapter0.fromJsonTree(json[1]), adapter1.fromJsonTree(json[2]))
			} else if (json.size() == 2) {
				if (json[1] is JsonObject) {
					return DungeonBrush.Tile(false, adapter.fromJsonTree(json[1]))
				} else {
					return DungeonBrush.Tile(false, adapter0.fromJsonTree(json[1]))
				}
			}

			throw IllegalArgumentException("Invalid tile modifier brush: $json")
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("front" in json) {
				return DungeonBrush.Tile(false, Registries.tiles.ref(json["front"].asString), adapterTiled.fromJsonTree(json))
			} else if (json["layer"].asString == "front" && "material" in json) {
				return DungeonBrush.Tile(false, Registries.tiles.ref(json["material"].asString), adapterTiled.fromJsonTree(json))
			}

			return null
		}
	},

	BACKGROUND("back") {
		private val adapter by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Tile.JsonData::class.java)
		}

		private val adapterTiled by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Tile.TiledData::class.java)
		}

		private val adapter0 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<TileDefinition>>() {})
		}

		private val adapter1 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<TileModifierDefinition>>() {})
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			if (json.size() == 3) {
				return DungeonBrush.Tile(true, adapter0.fromJsonTree(json[1]), adapter1.fromJsonTree(json[2]))
			} else if (json.size() == 2) {
				if (json[1] is JsonObject) {
					return DungeonBrush.Tile(true, adapter.fromJsonTree(json[1]))
				} else {
					return DungeonBrush.Tile(true, adapter0.fromJsonTree(json[1]))
				}
			}

			throw IllegalArgumentException("Invalid tile modifier brush: $json")
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("back" in json) {
				return DungeonBrush.Tile(true, Registries.tiles.ref(json["back"].asString), adapterTiled.fromJsonTree(json))
			} else if (json["layer"].asString == "back" && "material" in json) {
				return DungeonBrush.Tile(true, Registries.tiles.ref(json["material"].asString), adapterTiled.fromJsonTree(json))
			}

			return null
		}
	},

	OBJECT("object") {
		private val adapter0 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<ObjectDefinition>>() {})
		}

		private val adapter1 by lazy {
			Starbound.gson.getAdapter(DungeonBrush.WorldObject.Extra::class.java)
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			if (json.size() > 2) {
				return DungeonBrush.WorldObject(adapter0.fromJsonTree(json[1]), adapter1.fromJsonTree(json[2]))
			}

			return DungeonBrush.WorldObject(adapter0.fromJsonTree(json[1]))
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("object" in json) {
				val ref = Registries.worldObjects.ref(json["object"].asString)
				var direction = if ("tilesetDirection" in json) Direction.entries.first { it.jsonName == json["tilesetDirection"].asString } else Direction.RIGHT

				if ("flipX" in json)
					direction = direction.opposite

				var parameters = json.get("parameters")

				if (parameters == null || parameters.isJsonNull)
					parameters = JsonObject()
				else if (parameters is JsonPrimitive)
					parameters = Starbound.ELEMENTS_ADAPTER.objects.fromJson(parameters.asString)

				return DungeonBrush.WorldObject(ref, direction, parameters as JsonObject)
			}

			return null
		}
	},

	VEHICLE("vehicle") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.Vehicle(json[1].asString, if (json.size() >= 3) json[2].asJsonObject else JsonObject())
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("vehicle" in json) {
				return DungeonBrush.Vehicle(json["vehicle"].asString, json.get("parameters") { JsonObject() })
			}

			return null
		}
	},

	BIOME_ITEMS("biomeitems") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.BiomeItems
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("biomeitems" in json)
				return DungeonBrush.BiomeItems

			return null
		}
	},

	BIOME_TREE("biometree") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.BiomeTree
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("biometree" in json)
				return DungeonBrush.BiomeTree

			return null
		}
	},

	DROP_ITEM("item") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.DropItem(ItemDescriptor(json[1]), json[1] !is JsonObject)
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("item" in json) {
				return DungeonBrush.DropItem(ItemDescriptor(json["item"].asString, json.get("count", 1L), json.get("parameters") { JsonObject() }), "dont_randomize" !in json)
			}

			return null
		}
	},

	NPC("npc") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.NPC(json[1].asJsonObject)
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("npc" in json) {
				val brush = JsonObject()

				brush["kind"] = "npc"
				brush["species"] = json["npc"] // this may be a single species or a comma
				// separated list to be parsed later

				if ("seed" in json) {
					brush["seed"] = json["seed"]
				} else {
					brush["seed"] = "stable"
				}

				if ("typeName" in json) {
					brush["typeName"] = json["typeName"]
				}

				var parameters = json.get("parameters")

				if (parameters == null || parameters.isJsonNull)
					parameters = JsonObject()
				else if (parameters is JsonPrimitive)
					parameters = Starbound.ELEMENTS_ADAPTER.objects.fromJson(parameters.asString)

				brush["parameters"] = parameters
				return DungeonBrush.NPC(brush)
			} else if ("monster" in json) {
				val brush = JsonObject()

				brush["kind"] = "monster"
				brush["typeName"] = json["monster"]

				if ("seed" in json) {
					brush["seed"] = json["seed"]
				} else {
					brush["seed"] = "stable"
				}

				var parameters = json.get("parameters")

				if (parameters == null || parameters.isJsonNull)
					parameters = JsonObject()
				else if (parameters is JsonPrimitive)
					parameters = Starbound.ELEMENTS_ADAPTER.objects.fromJson(parameters.asString)

				brush["parameters"] = parameters
				return DungeonBrush.NPC(brush)
			}

			return null
		}
	},

	STAGEHAND("stagehand") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.Stagehand(json[1].asJsonObject)
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("stagehand" in json) {
				val brush = JsonObject()

				brush["type"] = json["stagehand"]

				var parameters = json.get("parameters")

				if (parameters == null || parameters.isJsonNull)
					parameters = JsonObject()
				else if (parameters is JsonPrimitive)
					parameters = Starbound.ELEMENTS_ADAPTER.objects.fromJson(parameters.asString)

				brush["parameters"] = parameters

				if ("broadcastArea" in json) // this is set properly as array inside TiledMap
					brush["parameters"].asJsonObject["broadcastArea"] = json["broadcastArea"]

				if (json["stagehand"].asString == "radiomessage" && "radiomessage" in json) // why lock behind radiomessage type?
					brush["parameters"].asJsonObject["radiomessage"] = json["radiomessage"]

				return DungeonBrush.Stagehand(brush)
			}

			return null
		}
	},

	SURFACE("surface") {
		private val adapter0 by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Surface::class.java)
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			return adapter0.fromJsonTree(if (json.size() == 1) JsonObject() else json[1])
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if (json["layer"].asString == "front" && "surface" in json) {
				val variant = json["surface"].asString.toIntOrNull() ?: 0
				val mod = json["mod"]?.asString?.let { Registries.tileModifiers.ref(it) } ?: BuiltinMetaMaterials.EMPTY_MOD.ref
				return DungeonBrush.Surface(variant, mod, false)
			}

			return null
		}
	},

	SURFACE_BACKGROUND("surfacebackground") {
		private val adapter0 by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Surface::class.java)
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			return adapter0.fromJsonTree(if (json.size() == 1) JsonObject() else json[1]).copy(isBackground = true)
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if (json["layer"].asString == "back" && "surface" in json) {
				val variant = json["surface"].asString.toIntOrNull() ?: 0
				val mod = json["mod"]?.asString?.let { Registries.tileModifiers.ref(it) } ?: BuiltinMetaMaterials.EMPTY_MOD.ref
				return DungeonBrush.Surface(variant, mod, true)
			}

			return null
		}
	},

	LIQUID("liquid") {
		private val adapter0 by lazy {
			Starbound.gson.getAdapter(object : TypeToken<Registry.Ref<LiquidDefinition>>() {})
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			if (json.size() == 1)
				throw JsonSyntaxException("No liquid specified")

			if (json.size() == 2)
				return DungeonBrush.Liquid(adapter0.fromJsonTree(json[1]))
			else if (json.size() == 3)
				return DungeonBrush.Liquid(adapter0.fromJsonTree(json[1]), 1f, json[2].asBoolean)
			else if (json.size() == 4)
				return DungeonBrush.Liquid(adapter0.fromJsonTree(json[1]), json[2].asFloat, json[3].asBoolean)
			else
				throw JsonSyntaxException("Invalid liquid data: $json")
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("liquid" in json) {
				val quantity = json["quantity"]?.asFloat ?: 1f
				val source = "source" in json
				return DungeonBrush.Liquid(Registries.liquid.ref(json["liquid"].asString), quantity, source)
			}

			return null
		}
	},

	WIRE("wire") {
		private val adapter0 by lazy {
			Starbound.gson.getAdapter(DungeonBrush.Wire::class.java)
		}

		override fun createLegacy(json: JsonArray): DungeonBrush {
			return adapter0.fromJsonTree(if (json.size() == 1) JsonObject() else json[1])
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("wire" in json) {
				return DungeonBrush.Wire(json["wire"].asString, "local" in json)
			}

			return null
		}
	},

	PLAYER_START("playerstart") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.info_player_start
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("playerstart" in json) {
				return DungeonBrush.info_player_start
			}

			return null
		}
	},

	DUNGEON_ID("dungeonid") {
		override fun createLegacy(json: JsonArray): DungeonBrush {
			return DungeonBrush.DungeonID(json[1].asInt, if (json.size() >= 3) json[2].asBoolean else false)
		}

		override fun readTiled(json: JsonObject): DungeonBrush? {
			if ("dungeonid" in json) {
				return DungeonBrush.DungeonID(json["dungeonid"].asString.toIntOrNull() ?: NO_DUNGEON_ID, false)
			}

			return null
		}
	}
	;

	abstract fun createLegacy(json: JsonArray): DungeonBrush
	abstract fun readTiled(json: JsonObject): DungeonBrush?
}
