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

import it.unimi.dsi.fastutil.ints.IntArrayList
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
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.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.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
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.value
import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MRecipes

class PlatePressBlockEntity(
	blockPos: BlockPos,
	blockState: BlockState,
	val isTwin: Boolean = false,
) : MatteryWorkerBlockEntity<ItemJob>(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, blockPos, blockState, ItemJob.CODEC, if (isTwin) 2 else 1) {
	override val upgrades = UpgradeContainer(this::markDirtyFast, if (isTwin) 4 else 3, UpgradeType.BASIC_PROCESSING)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(MachinesConfig.PLATE_PRESS)))
	val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer)
	val outputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer)

	val experience = ExperienceStorage(MachinesConfig.PLATE_PRESS::maxExperienceStored).also(::addNeighbourListener)
	val energyConfig = ConfigurableEnergy(energy)
	val itemConfig = ConfigurableItemHandler(
		input = inputContainer.handler(HandlerFilter.OnlyIn),
		output = outputContainer.handler(HandlerFilter.OnlyOut),
	)

	init {
		exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)

		savetables.stateful(::energy, ENERGY_KEY)
		savetables.stateful(::inputContainer)
		savetables.stateful(::outputContainer)
		savetables.stateful(::experience)
		savetables.stateful(::upgrades)
	}

	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		if (isTwin)
			return TwinPlatePressMenu(containerID, inventory, this)
		else
			return PlatePressMenu(containerID, inventory, this)
	}

	override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
		if (status.job.itemStack.isEmpty)
			return status.success()

		if (!outputContainer.fullyAddItem(status.job.itemStack, slots = IntArrayList.of(id)) && !outputContainer.fullyAddItem(status.job.itemStack))
			return status.noItem()

		experience.storeExperience(status.experience, this)
	}

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

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

		val recipe = level.recipeManager
			.byType(MRecipes.PLATE_PRESS)
			.values
			.iterator()
			.filter { it.value.matches(inputContainer, id) }
			.maybe()?.value ?: return JobContainer.noItem()

		val toProcess = inputContainer[id].count.coerceAtMost(1 + upgrades.processingItems)

		inputContainer[id].shrink(toProcess)
		inputContainer.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))
	}

	override fun tick() {
		if (isTwin && balanceInputs) {
			inputContainer.balance()
		}

		super.tick()
	}
}
