package ru.dbotthepony.mc.otm.android

import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.registries.DeferredRegister
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.getValue
import ru.dbotthepony.mc.otm.data.SingletonCodec
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.registry.RegistryDelegate

object AndroidResearchResults {
	private val registrar = DeferredRegister.create(AndroidResearchResult.registryKey, OverdriveThatMatters.MOD_ID)

	init {
		registrar.register("feature") { AndroidResearchResult.Feature.Companion }
		registrar.register("feature_level") { AndroidResearchResult.FeatureLevel.Companion }
	}

	private object NanobotsArmorStrength : AndroidResearchResult.Singleton<NanobotsArmorStrength> {
		override val codec: Codec<NanobotsArmorStrength> = SingletonCodec(this)
		override val type: AndroidResearchResult.Type<*>
			get() = this

		override fun onResearched(research: AndroidResearch) {
			val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return
			feature.strength++
		}

		override fun onUnResearched(research: AndroidResearch) {
			val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return
			feature.strength--
		}
	}

	private object NanobotsArmorSpeed : AndroidResearchResult.Singleton<NanobotsArmorSpeed> {
		override val codec: Codec<NanobotsArmorSpeed> = SingletonCodec(this)
		override val type: AndroidResearchResult.Type<*>
			get() = this

		override fun onResearched(research: AndroidResearch) {
			val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return
			feature.speed++
		}

		override fun onUnResearched(research: AndroidResearch) {
			val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return
			feature.speed--
		}
	}

	val NANOBOTS_ARMOR_STRENGTH: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_strength") { NanobotsArmorStrength }
	val NANOBOTS_ARMOR_SPEED: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_speed") { NanobotsArmorSpeed }

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

interface AndroidResearchResult {
	interface Type<T : AndroidResearchResult> {
		val codec: Codec<T>
	}

	interface Singleton<T : Singleton<T>> : Type<T>, AndroidResearchResult

	/**
	 * Adds specific android feature [id] to target, does nothing if target already has specified feature
	 */
	class Feature(val id: ResourceLocation, val optional: Boolean = false) : AndroidResearchResult {
		val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")

		override val type: Type<*>
			get() = Companion

		override fun onResearched(research: AndroidResearch) {
			research.capability.addFeature(feature ?: return)
		}

		override fun onUnResearched(research: AndroidResearch) {
			research.capability.removeFeature(feature ?: return)
		}

		companion object : Type<Feature> {
			override val codec: Codec<Feature> by lazy {
				Codec
					.either(ResourceLocation.CODEC, RecordCodecBuilder.create<Feature> { // KT-52757
						it.group(
							ResourceLocation.CODEC.fieldOf("id").forGetter(Feature::id),
							Codec.BOOL.optionalFieldOf("optional", false).forGetter(Feature::optional)
						).apply(it, ::Feature)
					})
					.xmap<Feature>(
						{ c -> c.map({ Feature(it) }, { it }) },
						{ c -> if (c.optional) Either.left(c.id) else Either.right(c) }
					)
			}
		}
	}

	/**
	 * Increases level of specific android feature [id] by specified amount [levels]
	 */
	class FeatureLevel(val id: ResourceLocation, val optional: Boolean = false, val levels: Int = 1) : AndroidResearchResult {
		val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id")

		override val type: Type<*>
			get() = Companion

		override fun onResearched(research: AndroidResearch) {
			val get = research.capability.getFeature(feature ?: return)

			if (get == null) {
				LOGGER.warn("Unable to advance level of android feature $id for ${research.ply} because they have no such android feature.")
			} else {
				get.level += levels
			}
		}

		override fun onUnResearched(research: AndroidResearch) {
			val get = research.capability.getFeature(feature ?: return)

			if (get == null) {
				LOGGER.warn("Unable to decrease level of android feature $id for ${research.ply} because they have no such android feature.")
			} else {
				get.level += levels
			}
		}

		companion object : Type<FeatureLevel> {
			override val codec: Codec<FeatureLevel> by lazy {
				Codec
					.either(ResourceLocation.CODEC, RecordCodecBuilder.create<FeatureLevel> { // KT-52757
						it.group(
							ResourceLocation.CODEC.fieldOf("id").forGetter(FeatureLevel::id),
							Codec.BOOL.optionalFieldOf("optional", false).forGetter(FeatureLevel::optional),
							Codec.INT.optionalFieldOf("levels", 1).forGetter(FeatureLevel::levels),
						).apply(it, ::FeatureLevel)
					})
					.xmap(
						{ c -> c.map({ FeatureLevel(it) }, { it }) },
						{ c -> if (c.optional && c.levels == 1) Either.left(c.id) else Either.right(c) }
					)
			}
		}
	}

	val type: Type<*>

	/**
	 * Called when research is applied
	 */
	fun onResearched(research: AndroidResearch) {}

	/**
	 * Called when research is refunded
	 */
	fun onUnResearched(research: AndroidResearch) {}

	companion object {
		private val LOGGER = LogManager.getLogger()
		private val delegate = RegistryDelegate<Type<*>>("android_research_result") {
			disableSaving()
		}

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

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

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