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.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.Job
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.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.graph.matter.MatterGraph
import ru.dbotthepony.mc.otm.item.matter.MatterDustItem
import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.data.minRange
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode

class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState)
	: MatteryWorkerBlockEntity<MatterRecyclerBlockEntity.RecyclerJob>(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, RecyclerJob.CODEC) {

	class RecyclerJob(ticks: Double, powerUsage: Decimal, var totalMatter: Decimal) : Job(ticks, powerUsage) {
		companion object {
			val CODEC: Codec<RecyclerJob> by lazy {
				RecordCodecBuilder.create {
					plainCodec(it).and(DecimalCodec.minRange(Decimal.ZERO).fieldOf("totalMatter").forGetter(RecyclerJob::totalMatter)).apply(it, ::RecyclerJob)
				}
			}
		}
	}

	override val upgrades = UpgradeContainer(this::markDirtyFast, 3, UpgradeType.BASIC_MATTER)
	val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.OUTPUT, upgrades.matterCapacity(MachinesConfig.MatterRecycler.VALUES::matterCapacity)))
	val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer)

	val matterNode = SimpleMatterNode(matter = matter)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MatterRecycler.VALUES)))

	val itemConfig = ConfigurableItemHandler(input = container.handler(object : HandlerFilter {
		override fun canInsert(slot: Int, stack: ItemStack): Boolean {
			return stack.item is MatterDustItem
		}

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

	val energyConfig = ConfigurableEnergy(energy)

	init {
		exposeGlobally(MatteryCapability.MATTER, matter)
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)

		savetables.stateful(::energy, ENERGY_KEY)
		savetables.stateful(::container, INVENTORY_KEY)
		savetables.stateful(::matter, MATTER_STORAGE_KEY)
		savetables.stateful(::upgrades)
	}

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

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

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

	override fun onJobFinish(status: JobStatus<RecyclerJob>, id: Int) {
		val job = status.job

		if (job.totalMatter.isPositive) {
			val received = matter.receiveMatter(job.totalMatter, true)

			if (job.totalMatter != received)
				return status.noMatter()

			matter.receiveMatter(job.totalMatter, false)
			job.totalMatter -= received
		}
	}

	override fun computeNextJob(id: Int): JobContainer<RecyclerJob> {
		if (matter.missingMatter.isZero)
			return JobContainer.noMatter()

		val stack = container[0]

		if (stack.isEmpty || stack.item !is MatterDustItem)
			return JobContainer.noItem()

		val dustMatter = (stack.item as MatterDustItem).getMatterValue(stack.copy().also { it.count = 1 }) ?: return JobContainer.noItem()

		stack.shrink(1)
		container.setChanged(0)

		val actualMatter = dustMatter.matter * (0.4 + level!!.random.nextDouble() * 0.6)

		return JobContainer.success(
			RecyclerJob(
				(actualMatter / (MachinesConfig.MatterRecycler.MATTER_PER_TICK * (1.0 + upgrades.speedBonus))).toDouble(),
				MachinesConfig.MatterRecycler.VALUES.energyConsumption * (1.0 + upgrades.speedBonus),
				actualMatter
			)
		)
	}

	override fun onJobTick(status: JobStatus<RecyclerJob>, id: Int) {
		val job = status.job
		val toReceive = job.totalMatter.coerceAtMost(MachinesConfig.MatterRecycler.MATTER_PER_TICK * status.ticksAdvanced * (1.0 + upgrades.speedBonus))

		if (toReceive.isZero)
			return status.success()

		val received = matter.receiveMatter(toReceive, false)
		status.scale(received / toReceive)
		job.totalMatter -= received
	}

	override fun tick() {
		super.tick()

		val graph = matterNode.graph as MatterGraph? ?: return
		val received = graph.receiveMatter(matter.storedMatter, false)

		if (!received.isZero) {
			matter.extractMatter(received, false)
		}
	}
}
