package ru.dbotthepony.mc.otm.android

import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import net.neoforged.bus.api.IEventBus
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.registry.game.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.registry.RegistryDelegate

object AndroidResearchResults {
	private val registrar = MDeferredRegister(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: MapCodec<NanobotsArmorStrength> = MapCodec.unit(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: MapCodec<NanobotsArmorSpeed> = MapCodec.unit(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 }

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

interface AndroidResearchResult {
	interface Type<T : AndroidResearchResult> {
		val codec: MapCodec<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.get(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: MapCodec<Feature> by lazy {
				RecordCodecBuilder.mapCodec<Feature> { // KT-52757
					it.group(
						ResourceLocation.CODEC.fieldOf("id").forGetter(Feature::id),
						Codec.BOOL.optionalFieldOf("optional", false).forGetter(Feature::optional)
					).apply(it, ::Feature)
				}
			}
		}
	}

	/**
	 * 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.get(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: MapCodec<FeatureLevel> by lazy {
				RecordCodecBuilder.mapCodec<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)
				}
			}
		}
	}

	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") {}

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

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

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