package ru.dbotthepony.mc.otm.block.entity.tech

import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.AbstractCookingRecipe
import net.minecraft.world.item.crafting.BlastingRecipe
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.item.crafting.SmeltingRecipe
import net.minecraft.world.item.crafting.SmokingRecipe
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.ItemJob
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
import ru.dbotthepony.mc.otm.capability.UpgradeType
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.config.WorkerBalanceValues
import ru.dbotthepony.mc.otm.container.CombinedContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.container.balance
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.maybe
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu
import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe
import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MRecipes

sealed class AbstractPoweredFurnaceBlockEntity<P : AbstractCookingRecipe, S : MatteryCookingRecipe>(
	type: BlockEntityType<*>,
	blockPos: BlockPos,
	blockState: BlockState,
	val recipeType: RecipeType<P>,
	val secondaryRecipeType: RecipeType<S>?,
	val config: WorkerBalanceValues,
	maxJobs: Int = 2
) : MatteryWorkerBlockEntity<ItemJob>(type, blockPos, blockState, ItemJob.CODEC, maxJobs) {
	final override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC_PROCESSING)
	final override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config)))

	val inputs = immutableList(maxJobs) { MatteryContainer(this::itemContainerUpdated, 1) }
	val outputs = immutableList(maxJobs) { MatteryContainer(this::itemContainerUpdated, 1) }

	init {
		inputs.forEach { addDroppableContainer(it) }
		outputs.forEach { addDroppableContainer(it) }
	}

	inner class SyncSlot {
		var inputItem by synchronizer.item(observe = false)
		var outputItem by synchronizer.item(observe = false)
		var progress by synchronizer.float().property
		var visualRotation = 0f
	}

	var lastRender = 0L

	val syncSlots = immutableList(maxJobs) { SyncSlot() }

	val experience = ExperienceStorage(config::maxExperienceStored).also(::addNeighbourListener)
	val energyConfig = ConfigurableEnergy(energy)
	val itemConfig = ConfigurableItemHandler(
		input = CombinedItemHandler(inputs.map { it.handler(HandlerFilter.OnlyIn) }),
		output = CombinedItemHandler(outputs.map { it.handler(HandlerFilter.OnlyOut) }),
		battery = batteryItemHandler
	)

	init {
		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)

		savetables.stateful(::upgrades)
		savetables.stateful(::energy)

		savetables.stateful(::experience)

		savetables.stateful(inputs, "input")
		savetables.stateful(outputs, "output")
	}

	private val combined = CombinedContainer(inputs)

	override fun tick() {
		if (balanceInputs) {
			combined.balance()
		}

		super.tick()
	}

	override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
		if (outputs[id].fullyAddItem(status.job.itemStack)) {
			experience.storeExperience(status.experience, this)
			syncSlots[id].inputItem = ItemStack.EMPTY
			syncSlots[id].outputItem = ItemStack.EMPTY
			syncSlots[id].progress = 0f
		} else {
			status.noItem()
		}
	}

	override fun onJobTick(status: JobStatus<ItemJob>, id: Int) {
		super.onJobTick(status, id)
		syncSlots[id].progress = status.workProgress
	}

	override fun computeNextJob(id: Int): JobContainer<ItemJob> {
		if (!energy.batteryLevel.isPositive)
			return JobContainer.noEnergy()

		if (inputs[id].isEmpty)
			return JobContainer.noItem()

		val level = level as? ServerLevel ?: return JobContainer.failure()

		if (secondaryRecipeType != null) {
			val recipe = level.recipeManager
				.byType(secondaryRecipeType)
				.values
				.iterator()
				.filter { it.value.matches(inputs[id], 0) }
				.maybe()?.value

			if (recipe != null) {
				val toProcess = inputs[id][0].count.coerceAtMost(1 + upgrades.processingItems)

				inputs[id][0].shrink(toProcess)
				inputs[id].setChanged(id)

				return JobContainer.success(
					ItemJob(
						recipe.getResultItem(level.registryAccess()).copyWithCount(toProcess),
						recipe.workTime * MachinesConfig.PLATE_PRESS.workTimeMultiplier,
						MachinesConfig.PLATE_PRESS.energyConsumption * toProcess,
						experience = recipe.experience.sample(level.random) * toProcess))
			}
		}

		return level.recipeManager.getRecipeFor(recipeType, inputs[id], level).map {
			val output = it.value.assemble(inputs[id], level.registryAccess())
			val inputItem = inputs[id][0].copyWithCount(1)
			val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1)
			inputs[id][0].shrink(toProcess)

			syncSlots[id].inputItem = inputItem
			syncSlots[id].outputItem = output.copy()
			syncSlots[id].progress = 0f

			JobContainer.success(ItemJob(
				output.copyWithCount(toProcess), it.value.cookingTime * config.workTimeMultiplier, config.energyConsumption * toProcess, it.value.experience * toProcess
			))
		}.orElse(JobContainer.noItem())
	}
}

class PoweredFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity<SmeltingRecipe, MatteryCookingRecipe>(MBlockEntities.POWERED_FURNACE, blockPos, blockState, RecipeType.SMELTING, null, MachinesConfig.POWERED_FURNACE) {
	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return PoweredFurnaceMenu.furnace(containerID, inventory, this)
	}
}

class PoweredBlastFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity<BlastingRecipe, MatteryCookingRecipe>(MBlockEntities.POWERED_BLAST_FURNACE, blockPos, blockState, RecipeType.BLASTING, null, MachinesConfig.POWERED_FURNACE) {
	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return PoweredFurnaceMenu.blasting(containerID, inventory, this)
	}
}

class PoweredSmokerBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity<SmokingRecipe, MicrowaveRecipe>(MBlockEntities.POWERED_SMOKER, blockPos, blockState, RecipeType.SMOKING, MRecipes.MICROWAVE, MachinesConfig.POWERED_FURNACE) {
	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return PoweredFurnaceMenu.smoking(containerID, inventory, this)
	}
}
