package ru.dbotthepony.mc.otm.triggers

import com.mojang.serialization.Codec
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.advancements.Criterion
import net.minecraft.advancements.CriterionTrigger
import net.minecraft.advancements.CriterionTriggerInstance
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.CriterionValidator
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.PlayerAdvancements
import net.minecraft.server.level.ServerPlayer
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.toImmutableList
import java.util.*
import java.util.function.Predicate

// has slightly less memory footprint than vanilla SimpleCriterionTrigger
abstract class MCriterionTrigger<T : MCriterionTrigger<T>.AbstractInstance>(val id: ResourceLocation) : CriterionTrigger<T> {
	private val listeners = Reference2ObjectOpenHashMap<PlayerAdvancements, ObjectOpenHashSet<CriterionTrigger.Listener<T>>>()

	override fun addPlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener<T>) {
		listeners.computeIfAbsent(advancements, Reference2ObjectFunction { ObjectOpenHashSet() }).add(listener)
	}

	override fun removePlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener<T>) {
		listeners[advancements]?.remove(listener)
	}

	override fun removePlayerListeners(advancements: PlayerAdvancements) {
		listeners.remove(advancements)
	}

	protected abstract val codec: Codec<T>

	final override fun codec(): Codec<T> {
		return codec
	}

	fun trigger(player: ServerPlayer, predicate: Predicate<T> = Predicate { true }) {
		val advancements = player.advancements
		val listeners = listeners[advancements] ?: return

		val context = EntityPredicate.createContext(player, player)

		listeners.iterator()
			.filter { predicate.test(it.trigger) && it.trigger.playerPredicate.map { it.matches(context) }.orElse(true) }
			.toImmutableList()
			.forEach { it.run(advancements) }
	}

	abstract inner class AbstractInstance(val playerPredicate: Optional<ContextAwarePredicate>) : CriterionTriggerInstance {
		fun criterion() = Criterion(this@MCriterionTrigger, this as T)

		override fun validate(validator: CriterionValidator) {
			validator.validateEntity(playerPredicate, ".player")
		}
	}
}
