package ru.dbotthepony.mc.otm.triggers

import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import net.minecraft.advancements.critereon.*
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.monster.ElderGuardian
import net.minecraftforge.event.entity.living.LivingDeathEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.core.asIterable
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.stream
import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.function.Predicate

object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instance>() {
	val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android")

	override fun getId(): ResourceLocation {
		return ID
	}

	override fun createInstance(pJson: JsonObject, pPlayer: ContextAwarePredicate, pContext: DeserializationContext): Instance {
		return Instance(
			predicate = EntityPredicate.fromJson(pJson, "predicate", pContext),
			playerPredicate = pPlayer,
			featurePredicate = (pJson["feature_predicate"] as? JsonObject)?.let(PredicateType::from) ?: throw JsonSyntaxException("Invalid 'feature_predicate': ${pJson["feature_predicate"]}")
		)
	}

	enum class PredicateType(val factory: (JsonObject) -> FeaturePredicate) {
		ALWAYS({ Always }),
		HAS(::Has),
		NOT(::Not),
		AND(::And),
		OR(::OrPredicate);

		companion object {
			fun from(json: JsonObject): FeaturePredicate {
				return when (val type = json["type"]?.asString?.lowercase()) {
					null -> throw JsonSyntaxException("Missing feature predicate 'type'")
					"has" -> HAS.factory(json)
					"and" -> AND.factory(json)
					"not" -> NOT.factory(json)
					"or" -> OR.factory(json)
					"always" -> ALWAYS.factory(json)
					else -> throw JsonSyntaxException("Unknown feature predicate $type")
				}
			}
		}
	}

	abstract class FeaturePredicate : Predicate<MatteryPlayerCapability> {
		abstract val type: PredicateType

		open fun toJson(): JsonObject {
			return JsonObject().also {
				it["type"] = type.name.lowercase()
			}
		}
	}

	object Always : FeaturePredicate() {
		override val type: PredicateType
			get() = PredicateType.ALWAYS

		override fun test(t: MatteryPlayerCapability): Boolean {
			return true
		}
	}

	class Has : FeaturePredicate {
		private val resolved by lazy { MRegistry.ANDROID_FEATURES.getValue(name) }
		val name: ResourceLocation

		constructor(name: ResourceLocation) {
			this.name = name
		}

		constructor(value: JsonObject) {
			this.name = ResourceLocation.tryParse(value["name"]?.asString ?: throw JsonSyntaxException("Invalid android feature name: ${value["name"]}")) ?: throw JsonSyntaxException("Invalid android feature name ${value["name"]}")
		}

		override val type: PredicateType
			get() = PredicateType.HAS

		override fun test(t: MatteryPlayerCapability): Boolean {
			return t.hasFeature(resolved ?: return false)
		}

		override fun toJson(): JsonObject {
			return super.toJson().also {
				it["name"] = name.toString()
			}
		}
	}

	class Not(val parent: FeaturePredicate) : FeaturePredicate() {
		constructor(input: JsonObject) : this(PredicateType.from(input["parent"] as? JsonObject ?: throw JsonSyntaxException("Invalid parent")))

		override val type: PredicateType
			get() = PredicateType.NOT

		override fun test(t: MatteryPlayerCapability): Boolean {
			return !parent.test(t)
		}

		override fun toJson(): JsonObject {
			return super.toJson().also {
				it["parent"] = parent.toJson()
			}
		}
	}

	class And(children: Iterable<FeaturePredicate>) : FeaturePredicate() {
		constructor(input: JsonObject) : this((input["children"] as? JsonArray)?.stream()?.map { PredicateType.from(it as? JsonObject ?: throw JsonSyntaxException("Invalid element in children list: $it")) }?.asIterable() ?: throw JsonSyntaxException("Invalid children list"))

		val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)

		override val type: PredicateType
			get() = PredicateType.AND

		override fun test(t: MatteryPlayerCapability): Boolean {
			return children.all { it.test(t) }
		}

		override fun toJson(): JsonObject {
			return super.toJson().also {
				it["children"] = JsonArray().also {
					for (child in children) {
						it.add(child.toJson())
					}
				}
			}
		}
	}

	class OrPredicate(children: Iterable<FeaturePredicate>) : FeaturePredicate() {
		constructor(input: JsonObject) : this((input["children"] as? JsonArray)?.stream()?.map { PredicateType.from(it as? JsonObject ?: throw JsonSyntaxException("Invalid element in children list: $it")) }?.asIterable() ?: throw JsonSyntaxException("Invalid children list"))

		val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)

		override val type: PredicateType
			get() = PredicateType.OR

		override fun test(t: MatteryPlayerCapability): Boolean {
			return children.any { it.test(t) }
		}

		override fun toJson(): JsonObject {
			return super.toJson().also {
				it["children"] = JsonArray().also {
					for (child in children) {
						it.add(child.toJson())
					}
				}
			}
		}
	}

	class Instance(
		val predicate: ContextAwarePredicate = ContextAwarePredicate.ANY,
		val featurePredicate: FeaturePredicate = Always,
		playerPredicate: ContextAwarePredicate = ContextAwarePredicate.ANY,
	) : AbstractCriterionTriggerInstance(ID, playerPredicate) {
		override fun serializeToJson(pConditions: SerializationContext): JsonObject {
			return super.serializeToJson(pConditions).also {
				it["predicate"] = predicate.toJson(pConditions)
				it["feature_predicate"] = featurePredicate.toJson()
			}
		}
	}

	fun onKill(event: LivingDeathEvent) {
		if (event.entity is ElderGuardian) {
			val killer = event.source.entity

			if (killer is ServerPlayer) {
				val data = killer.matteryPlayer ?: return

				if (data.isAndroid) {
					val context = EntityPredicate.createContext(killer, event.entity)

					trigger(killer) {
						it.predicate.matches(context) &&
						it.featurePredicate.test(data)
					}
				}
			}
		}
	}
}
