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

import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.ExperienceOrb
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.crafting.AbstractCookingRecipe
import net.minecraft.world.item.crafting.RecipeType
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.registry.MBlockEntities

class PoweredFurnaceBlockEntity(
	type: BlockEntityType<PoweredFurnaceBlockEntity>,
	blockPos: BlockPos,
	blockState: BlockState,
	val recipeType: RecipeType<out AbstractCookingRecipe>,
	val secondaryRecipeType: (() -> RecipeType<out MatteryCookingRecipe>)?,
	val config: WorkerBalanceValues
) : MatteryWorkerBlockEntity<ItemJob>(type, blockPos, blockState, ItemJob.CODEC, 2) {
	override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC_PROCESSING)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config)))

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

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

	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 createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
		return when (type) {
			MBlockEntities.POWERED_FURNACE -> PoweredFurnaceMenu.furnace(containerID, inventory, this)
			MBlockEntities.POWERED_BLAST_FURNACE -> PoweredFurnaceMenu.blasting(containerID, inventory, this)
			MBlockEntities.POWERED_SMOKER -> PoweredFurnaceMenu.smoking(containerID, inventory, this)
			else -> null
		}
	}

	override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
		if (outputs[id].fullyAddItem(status.job.itemStack)) {
			experience.storeExperience(status.experience, this)
		} else {
			status.noItem()
		}
	}

	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.invoke() as RecipeType<MatteryCookingRecipe>)
				.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 as RecipeType<AbstractCookingRecipe>, inputs[id], level).map {
			val output = it.value.assemble(inputs[id], level.registryAccess())
			val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1)
			inputs[id][0].shrink(toProcess)

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