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

import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
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 net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.MachineJob
import ru.dbotthepony.mc.otm.config.ConciseBalanceValues
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl
import ru.dbotthepony.mc.otm.capability.MatteryCapability
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.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
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.core.math.DecimalConfigValue
import ru.dbotthepony.mc.otm.core.math.defineDecimal
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.core.util.WriteOnce
import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.math.set
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode

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

	class RecyclerJob : MachineJob {
		var totalMatter: Decimal

		constructor(
			ticks: Double,
			powerUsage: Decimal,
			totalMatter: Decimal
		) : super(ticks, powerUsage) {
			this.totalMatter = totalMatter
		}

		constructor(tag: CompoundTag) : super(tag) {
			this.totalMatter = tag.getDecimal(KEY)
		}

		override fun serializeNBT(): CompoundTag {
			return super.serializeNBT().also {
				it[KEY] = totalMatter
			}
		}

		companion object {
			const val KEY = "totalMatter"
		}
	}

	val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.OUTPUT, ::CAPACITY))
	val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer)

	val matterNode = SimpleMatterNode(matter = matter)
	val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, ENERGY_CONFIG))

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

	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(job: RecyclerJob, id: Int): JobStatus {
		// вся логика в onWorkTick
		return JobStatus.SUCCESS
	}

	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)
		return JobContainer.success(RecyclerJob(dustMatter.matter.toDouble() * TICKS_PER_MATTER, POWER_CONSUMPTION, dustMatter.matter * (0.4 + level!!.random.nextDouble() * 0.6)))
	}

	override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: RecyclerJob, id: Int): JobStatus {
		val receive = job.totalMatter / job.ticks

		if (receive.isZero)
			return JobStatus.SUCCESS

		val received = matter.receiveMatter(receive, true)

		if (receive != received)
			return JobStatus.FAILURE_MATTER

		matter.receiveMatter(receive, false)
		job.totalMatter -= received
		return JobStatus.SUCCESS
	}

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

	companion object {
		private var _CAPACITY: DecimalConfigValue by WriteOnce()
		private var _POWER_CONSUMPTION: DecimalConfigValue by WriteOnce()
		private var _TICKS_PER_MATTER: ConfigValue<Double> by WriteOnce()

		var ENERGY_CONFIG: ConciseBalanceValues by WriteOnce()
			private set

		private val CAPACITY get() = _CAPACITY.get()
		private val POWER_CONSUMPTION get() = _POWER_CONSUMPTION.get()
		private val TICKS_PER_MATTER: Double get() = _TICKS_PER_MATTER.get()

		fun registerConfig(builder: ForgeConfigSpec.Builder) {
			builder.push(MNames.MATTER_RECYCLER)

			ENERGY_CONFIG = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(80_000))

			_CAPACITY = builder.defineDecimal("matterCapacity", Decimal(500), Decimal.ONE)
			_POWER_CONSUMPTION = builder.defineDecimal("powerConsumption", Decimal(80), Decimal.ONE)
			_TICKS_PER_MATTER = builder.define("ticksPerMatter", 2.0)

			builder.pop()
		}
	}
}
