package ru.dbotthepony.mc.otm.compat.jei

import it.unimi.dsi.fastutil.ints.Int2IntArrayMap
import it.unimi.dsi.fastutil.ints.IntArraySet
import mezz.jei.api.IModPlugin
import mezz.jei.api.JeiPlugin
import mezz.jei.api.constants.RecipeTypes
import mezz.jei.api.constants.VanillaTypes
import mezz.jei.api.gui.handlers.IGuiContainerHandler
import mezz.jei.api.gui.ingredient.IRecipeSlotView
import mezz.jei.api.gui.ingredient.IRecipeSlotsView
import mezz.jei.api.helpers.IJeiHelpers
import mezz.jei.api.ingredients.ITypedIngredient
import mezz.jei.api.recipe.RecipeIngredientRole
import mezz.jei.api.recipe.transfer.IRecipeTransferError
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler
import mezz.jei.api.recipe.transfer.IRecipeTransferInfo
import mezz.jei.api.registration.IGuiHandlerRegistration
import mezz.jei.api.registration.IRecipeCatalystRegistration
import mezz.jei.api.registration.IRecipeCategoryRegistration
import mezz.jei.api.registration.IRecipeRegistration
import mezz.jei.api.registration.IRecipeTransferRegistration
import mezz.jei.api.runtime.IClickableIngredient
import net.minecraft.client.renderer.Rect2i
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingRecipe
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.toImmutableList
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.*
import java.util.stream.Collectors
import kotlin.collections.ArrayList
import kotlin.jvm.optionals.getOrNull
import kotlin.properties.Delegates

var isJeiLoaded = false
	private set

@JeiPlugin
@Suppress("unused")
class JEIPlugin : IModPlugin {
	companion object {
		private val LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, "jei_plugin")

		var helpers: IJeiHelpers by Delegates.notNull()
			private set
	}

	init {
		isJeiLoaded = true
	}

	override fun getPluginUid(): ResourceLocation {
		return LOCATION
	}

	override fun registerRecipeCatalysts(registration: IRecipeCatalystRegistration) {
		registration.addRecipeCatalyst(ItemStack(MItems.CHEMICAL_GENERATOR), RecipeTypes.FUELING)
		registration.addRecipeCatalyst(ItemStack(MItems.POWERED_FURNACE), RecipeTypes.SMELTING)
		registration.addRecipeCatalyst(ItemStack(MItems.ExopackUpgrades.SMELTING_UPGRADE), RecipeTypes.SMELTING)
		registration.addRecipeCatalyst(ItemStack(MItems.POWERED_BLAST_FURNACE), RecipeTypes.BLASTING)
		registration.addRecipeCatalyst(ItemStack(MItems.POWERED_SMOKER), RecipeTypes.SMOKING)
		registration.addRecipeCatalyst(ItemStack(MItems.ExopackUpgrades.CRAFTING_UPGRADE), RecipeTypes.CRAFTING)
		registration.addRecipeCatalyst(ItemStack(MItems.PLATE_PRESS), PlatePressRecipeCategory.recipeType)
	}

	override fun registerCategories(registration: IRecipeCategoryRegistration) {
		helpers = registration.jeiHelpers
		registration.addRecipeCategories(PlatePressRecipeCategory)
	}

	override fun registerRecipes(registration: IRecipeRegistration) {
		val level = minecraft.level ?: throw NullPointerException("No ClientLevel. OLOLOLOLOLOLO")

		registration.addRecipes(PlatePressRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PLATE_PRESS).filter { !it.isIncomplete })
	}

	override fun registerRecipeTransferHandlers(registration: IRecipeTransferRegistration) {
		val helper = registration.transferHelper

		registration.addRecipeTransferHandler(object : IRecipeTransferHandler<ExoPackInventoryMenu, CraftingRecipe> {
			private val transfer = helper.createUnregisteredRecipeTransferHandler(object : IRecipeTransferInfo<ExoPackInventoryMenu, CraftingRecipe> {
				override fun getContainerClass(): Class<out ExoPackInventoryMenu> {
					return ExoPackInventoryMenu::class.java
				}

				override fun getMenuType(): Optional<MenuType<ExoPackInventoryMenu>> {
					return Optional.empty()
				}

				override fun getRecipeType(): mezz.jei.api.recipe.RecipeType<CraftingRecipe> {
					return RecipeTypes.CRAFTING
				}

				override fun canHandle(container: ExoPackInventoryMenu, recipe: CraftingRecipe): Boolean {
					return true
				}

				override fun getRecipeSlots(
					container: ExoPackInventoryMenu,
					recipe: CraftingRecipe
				): List<Slot> {
					return container.craftingSlots
				}

				override fun getInventorySlots(
					container: ExoPackInventoryMenu,
					recipe: CraftingRecipe
				): List<Slot> {
					return container.playerInventorySlots
				}
			})

			override fun getContainerClass(): Class<out ExoPackInventoryMenu> {
				return ExoPackInventoryMenu::class.java
			}

			override fun getMenuType(): Optional<MenuType<ExoPackInventoryMenu>> {
				return Optional.empty()
			}

			override fun getRecipeType(): mezz.jei.api.recipe.RecipeType<CraftingRecipe> {
				return RecipeTypes.CRAFTING
			}

			private val validSlots = IntArraySet().also {
				it.add(0)
				it.add(1)
				it.add(3)
				it.add(4)
			}

			private val directMap = Int2IntArrayMap().also {
				for (i in 0 .. 8) {
					it.put(i, i)
				}
			}

			private val smallMap = Int2IntArrayMap().also {
				var i = 0
				it.put(0, i++)
				it.put(1, i++)
				it.put(3, i++)
				it.put(4, i)
			}

			override fun transferRecipe(
				container: ExoPackInventoryMenu,
				recipe: CraftingRecipe,
				recipeSlots: IRecipeSlotsView,
				player: Player,
				maxTransfer: Boolean,
				doTransfer: Boolean
			): IRecipeTransferError? {
				val inputs = recipeSlots.getSlotViews(RecipeIngredientRole.INPUT)

				if (inputs.size != 9) {
					return IRecipeTransferError { IRecipeTransferError.Type.INTERNAL }
				}

				if (container.craftingSlots.size != 9) {
					for ((i, ingredient) in inputs.withIndex()) {
						if (!ingredient.isEmpty && i !in validSlots) {
							return helper.createUserErrorWithTooltip(TranslatableComponent("jei.tooltip.error.recipe.transfer.too.large.player.inventory"))
						}
					}

					val filteredInputs = ArrayList<IRecipeSlotView>()

					for ((i, ingredient) in inputs.withIndex()) {
						if (i in validSlots) {
							filteredInputs.add(ingredient)
						}
					}

					val outputs = recipeSlots.getSlotViews(RecipeIngredientRole.OUTPUT).toImmutableList()
					val catalysts = recipeSlots.getSlotViews(RecipeIngredientRole.CATALYST).toImmutableList()
					val render = recipeSlots.getSlotViews(RecipeIngredientRole.RENDER_ONLY).toImmutableList()

					val combine = ArrayList<IRecipeSlotView>(filteredInputs.size + outputs.size + render.size)

					combine.addAll(filteredInputs)
					combine.addAll(outputs)
					combine.addAll(render)

					val combined = combine.toImmutableList()

					val newView = object : IRecipeSlotsView {
						override fun getSlotViews(): List<IRecipeSlotView> {
							return combined
						}

						override fun getSlotViews(role: RecipeIngredientRole): List<IRecipeSlotView> {
							return when (role) {
								RecipeIngredientRole.INPUT -> filteredInputs
								RecipeIngredientRole.OUTPUT -> outputs
								RecipeIngredientRole.CATALYST -> catalysts
								RecipeIngredientRole.RENDER_ONLY -> render
							}
						}

						override fun findSlotByName(slotName: String): Optional<IRecipeSlotView> {
							return combined.stream().filter { it.slotName.orElse(null) == slotName }.findAny()
						}
					}

					return this.transfer.transferRecipe(container, recipe, newView, player, maxTransfer, doTransfer)
				}

				return this.transfer.transferRecipe(container, recipe, recipeSlots, player, maxTransfer, doTransfer)
			}
		}, RecipeTypes.CRAFTING)
	}

	override fun registerGuiHandlers(registration: IGuiHandlerRegistration) {
		registration.addGenericGuiContainerHandler(MatteryScreen::class.java, object : IGuiContainerHandler<MatteryScreen<*>> {
			override fun getGuiExtraAreas(containerScreen: MatteryScreen<*>): MutableList<Rect2i> {
				return containerScreen.panelsView.stream()
					.map {
						it.performLayoutIfNeeded()
						it.updateAbsoluteCoordinates()
						it.calculateAbsoluteObstructingRectangles()
					}
					.flatMap { it.stream() }
					.map { it.toIntRect() }
					.collect(Collectors.toList())
			}

			override fun getClickableIngredientUnderMouse(
				containerScreen: MatteryScreen<*>,
				mouseX: Double,
				mouseY: Double
			): Optional<IClickableIngredient<*>> {
				return containerScreen.panelsView
					.stream()
					.map { it.findItemStack(mouseX.toFloat(), mouseY.toFloat(), ignoreMouseInputLock = true) }
					.filter { it.first != null }
					.findAny()
					.flatMap { a -> helpers.ingredientManager.createTypedIngredient(VanillaTypes.ITEM_STACK, a.second).map { a.first to it } }
					.map {
						object : IClickableIngredient<ItemStack> {
							override fun getTypedIngredient(): ITypedIngredient<ItemStack> {
								return it.second
							}

							override fun getArea(): Rect2i {
								return it.first!!.calculateAbsoluteRectangle().toIntRect()
							}
						}
					}
			}
		})
	}
}
