package ru.dbotthepony.mc.otm.matter

import com.google.gson.JsonParseException
import com.mojang.datafixers.util.Either
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.Item
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.registries.DeferredRegister
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.registry.RegistryDelegate
import java.util.*

abstract class AbstractRegistryAction(
	val id: Either<ResourceLocation, TagKey<Item>>,
	val errorOnFailure: Boolean = false,
	val priority: Optional<Int> = Optional.empty(),
) : Comparable<AbstractRegistryAction> {
	interface Type<T : AbstractRegistryAction> {
		val codec: Codec<T>
	}

	fun update(registry: MutableCollection<IMutableRegistryEntry>, source: ResourceLocation) {
		if (!checkConditions()) {
			return
		}

		doUpdate(registry, source)
	}

	protected abstract fun doUpdate(registry: MutableCollection<IMutableRegistryEntry>, source: ResourceLocation)

	abstract val type: Type<*>

	open fun checkConditions(): Boolean {
		return true
	}

	protected inline fun fail(reason: () -> String) {
		if (errorOnFailure) {
			throw JsonParseException(reason.invoke())
		}
	}

	fun canReplace(other: AbstractRegistryAction?): Boolean {
		if (other == null)
			return true

		if (other.priority.isPresent && priority.isPresent)
			return other.priority.get() <= priority.get()

		return other.priority.isEmpty
	}

	final override fun compareTo(other: AbstractRegistryAction): Int {
		if (other.priority.isEmpty) {
			if (priority.isEmpty) {
				return 0
			}

			return 1
		}

		if (priority.isEmpty) {
			return -1
		}

		return priority.get().compareTo(other.priority.get())
	}

	object TargetCodec : Codec<Either<ResourceLocation, TagKey<Item>>> {
		private val tagCodec = TagKey.codec(Registries.ITEM)

		override fun <T : Any> encode(input: Either<ResourceLocation, TagKey<Item>>, ops: DynamicOps<T>, prefix: T): DataResult<T> {
			return DataResult.success(input.map(
				{
					ops.createString(it.toString())
				},
				{
					ops.createString("#" + it.location.toString())
				}
			))
		}

		override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Either<ResourceLocation, TagKey<Item>>, T>> {
			return ops.getStringValue(input).flatMap {
				if (it[0] == '#')
					tagCodec.decode(ops, ops.createString(it.substring(1))).map { Pair(Either.right(it.first), it.second) }
				else
					ResourceLocation.CODEC.decode(ops, input).map { Pair(Either.left(it.first), it.second) }
			}
		}
	}

	companion object {
		private val registryDelegate = RegistryDelegate<Type<*>>("matter_registry_action") { disableSaving(); disableSync() }

		val registryKey get() = registryDelegate.key
		val registry by registryDelegate

		private val registrar = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID)

		init {
			registrar.register("insert") { InsertAction.Companion }
			registrar.register("update") { UpdateAction.Companion }
			registrar.register("compute") { ComputeAction.Companion }
			registrar.register("delete") { DeleteAction.Companion }
			registrar.register("blacklist") { BlacklistAction.Companion }
		}

		internal fun register(bus: IEventBus) {
			registrar.register(bus)
			bus.addListener(registryDelegate::build)
		}

		val CODEC: Codec<AbstractRegistryAction> by lazy {
			registry.codec.dispatch({ it.type }, { it.codec })
		}
	}
}

