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

import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
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.Recipe
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.MachineItemJob
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.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.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.immutableList
import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu

class PoweredFurnaceBlockEntity(
	type: BlockEntityType<PoweredFurnaceBlockEntity>,
	blockPos: BlockPos,
	blockState: BlockState,
	val recipeType: RecipeType<out AbstractCookingRecipe>,
	val config: WorkerBalanceValues
) : MatteryWorkerBlockEntity<MachineItemJob>(type, blockPos, blockState, ::MachineItemJob, 2) {
	override val upgrades = UpgradeContainer(this::setChangedLight, 2, UpgradeType.BASIC_PROCESSING)
	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) }

	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
	)

	var experience = 0.0
		private set

	fun popExperience(player: ServerPlayer) {
		val whole = experience.toInt()

		if (whole > 0) {
			experience -= whole
			ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
		}
	}

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

		savetables.double(::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 PoweredFurnaceMenu(containerID, inventory, this)
	}

	override fun onJobFinish(job: MachineItemJob, id: Int): JobStatus {
		if (outputs[id].fullyAddItem(job.itemStack)) {
			experience += job.experience
			return JobStatus.SUCCESS
		}

		return JobStatus.FAILURE_ITEM
	}

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

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

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

		return level.recipeManager.getRecipeFor(recipeType as RecipeType<AbstractCookingRecipe>, inputs[id], level).map {
			val output = it.assemble(inputs[id], level.registryAccess())
			val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1)
			inputs[id][0].shrink(toProcess)

			JobContainer.success(MachineItemJob(
				output.copyWithCount(toProcess), it.cookingTime * config.workTimeMultiplier, config.powerConsumption * toProcess, it.experience * toProcess
			))
		}.orElse(JobContainer.noItem())
	}
}
