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

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
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.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.Job
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
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.capability.matter.MatterStorageImpl
import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.data.codec.DecimalCodec
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import ru.dbotthepony.mc.otm.registry.game.MItems

class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState)
	: MatteryWorkerBlockEntity<MatterDecomposerBlockEntity.DecomposerJob>(MBlockEntities.MATTER_DECOMPOSER, pos, state, DecomposerJob.CODEC) {

	class DecomposerJob(
		val toDust: Boolean,
		var matterValue: Decimal,
		ticks: Double,
	) : Job(ticks, MachinesConfig.MATTER_DECOMPOSER.energyConsumption) {
		companion object {
			val CODEC: Codec<DecomposerJob> by lazy {
				RecordCodecBuilder.create {
					it.group(
						Codec.BOOL.fieldOf("toDust").forGetter(DecomposerJob::toDust),
						DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(DecomposerJob::matterValue),
						Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(DecomposerJob::ticks)
					).apply(it, ::DecomposerJob)
				}
			}
		}
	}

	override val upgrades = makeUpgrades(4, UpgradeType.REPLICATOR)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, upgrades.transform(MachinesConfig.MATTER_DECOMPOSER)))
	val energyConfig = ConfigurableEnergy(energy)

	init {
		savetables.stateful(::energy, ENERGY_KEY)
		savetables.stateful(::upgrades)
	}

	val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.OUTPUT, upgrades.matterCapacity(MachinesConfig.MATTER_DECOMPOSER::matterCapacity)))
	val matterNode = SimpleMatterNode(matter = matter)

	init {
		exposeGlobally(MatteryCapability.MATTER_BLOCK, matter)
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
		savetables.stateful(::matter, MATTER_STORAGE_KEY)
	}

	// вход, выход
	val inputContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer)
	val outputContainer = MatteryContainer(::markDirtyFast, 2).also(::addDroppableContainer)

	val itemConfig = ConfigurableItemHandler(
		input = inputContainer.handler(object : HandlerFilter {
			override fun canInsert(slot: Int, stack: ItemStack): Boolean {
				return MatterManager.canDecompose(stack)
			}

			override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
				return false
			}
		}),

		output = outputContainer.handler(HandlerFilter.OnlyOut)
	)

	init {
		savetables.stateful(::inputContainer)
		savetables.stateful(::outputContainer)
	}

	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return MatterDecomposerMenu(containerID, inventory, this)
	}

	override fun onJobFinish(status: JobStatus<DecomposerJob>, id: Int) {
		if (status.job.toDust) {
			status.job.matterValue = MItems.MATTER_DUST.moveIntoContainer(status.job.matterValue, outputContainer, 0, 1)

			if (!status.job.matterValue.isZero)
				status.throttleFast()
		} else {
			status.job.matterValue -= matter.receiveMatter(status.job.matterValue, false)

			if (status.job.matterValue.isPositive)
				status.noMatter()
		}
	}

	override fun computeNextJob(id: Int): JobContainer<DecomposerJob> {
		val stack = inputContainer[0]

		if (!stack.isEmpty) {
			val copy = stack.copy()
			copy.count = 1

			if (MatterManager.canDecompose(copy)) {
				val matter = MatterManager.get(copy)
				stack.count--

				return JobContainer.success(
					DecomposerJob(
						(level?.otmRandom?.nextDouble() ?: 1.0) <= 0.2 * upgrades.failureMultiplier,
						matter.matter,
						matter.complexity * MachinesConfig.MATTER_DECOMPOSER.workTimeMultiplier
					)
				)
			}
		}

		return JobContainer.noItem()
	}

	override fun setRemoved() {
		super.setRemoved()
		matterNode.isValid = false
	}

	override fun setLevel(level: Level) {
		super.setLevel(level)
		matterNode.discover(this)
	}

	override fun tick() {
		super.tick()

		if (!matter.storedMatter.isZero) {
			matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false)
		}
	}
}
