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.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.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.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.math.Decimal
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(
	p_155229_: BlockPos,
	p_155230_: BlockState,
	val isTwin: Boolean = false,
) : MatteryWorkerBlockEntity<MachineItemJob>(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, p_155229_, p_155230_, ::MachineItemJob, if (isTwin) 2 else 1) {
	override val upgrades = UpgradeContainer(this::setChangedLight, if (isTwin) 4 else 3, UpgradeType.BASIC_PROCESSING)
	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)

	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)
		}
	}

	val energyConfig = ConfigurableEnergy(energy)
	val itemConfig = ConfigurableItemHandler(
		input = inputContainer.handler(HandlerFilter.OnlyIn),
		output = outputContainer.handler(HandlerFilter.OnlyOut),
	)

	init {
		savetable(::energy, ENERGY_KEY)
		savetable(::inputContainer)
		savetable(::outputContainer)
		savetables.double(::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(job: MachineItemJob, id: Int): JobStatus {
		if (job.itemStack.isEmpty)
			return JobStatus.SUCCESS

		if (!outputContainer.fullyAddItem(job.itemStack, start = id, end = id) && !outputContainer.fullyAddItem(job.itemStack))
			return JobStatus.FAILURE_ITEM

		experience = (experience + job.experience).coerceAtMost(100.0)
		return JobStatus.SUCCESS
	}

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

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

		val recipe = level.recipeManager
			.byType(MRecipes.PLATE_PRESS)
			.values
			.stream()
			.filter { it.matches(inputContainer, id) }
			.findAny().
			orElse(null) ?: return JobContainer.noItem()

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

		inputContainer[id].shrink(toProcess)
		inputContainer.setChanged(id)

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

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

		super.tick()
	}
}
