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

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.ItemJob
import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
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.PatternState
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.menu.matter.MatterScannerMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.graph.matter.MatterNode
import ru.dbotthepony.mc.otm.matter.MatterManager
import java.util.*
import kotlin.math.pow

class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
	MatteryWorkerBlockEntity<ItemJob>(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ItemJob.CODEC) {

	override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC)
	val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_SCANNER)))

	val itemConfig = ConfigurableItemHandler(inputOutput = container.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 jobEventLoops[0].isIdling
		}
	}))

	val energyConfig = ConfigurableEnergy(energy)

	val matterNode = object : MatterNode() {
		override fun onPatternAdded(state: PatternState) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN)
		}

		override fun onPatternRemoved(state: PatternState) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN)
		}

		override fun onPatternUpdated(newState: PatternState, oldState: PatternState) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN)
		}
	}

	init {
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)

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

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

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

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

	override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
		val stack = status.job.itemStack
		if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return status.success()

		var findState: PatternState? = null

		for (state in matterNode.graph.patterns.filter { it.item === stack.item }) {
			if (findState == null && state.researchPercent < 1.0) {
				findState = state
			} else if (findState != null && findState.researchPercent < state.researchPercent && state.researchPercent < 1.0) {
				findState = state
			}
		}

		val researchAdvance = MatterManager.getResearchAdvance(stack.item)

		val new: PatternState =
			if (findState != null) {
				PatternState(findState.id, stack.item, (findState.researchPercent + researchAdvance).coerceAtMost(1.0))
			} else {
				PatternState(UUID.randomUUID(), stack.item, researchAdvance)
			}

		if (matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = false).isFailed) {
			status.throttle()
		}
	}

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

		val stack = container.getItem(0)
		if (stack.isEmpty || !MatterManager.canDecompose(stack)) return JobContainer.noItem()

		var findState: PatternState? = null

		for (state in matterNode.graph.patterns.filter { it.item === stack.item }) {
			if (state.researchPercent < 1.0) {
				findState = state
			} else if (state.researchPercent >= 1.0) {
				return JobContainer.noItem()
			} else if (findState != null && findState.researchPercent < state.researchPercent) {
				findState = state
			}
		}

		val researchAdvance = MatterManager.getResearchAdvance(stack.item)

		val new: PatternState =
			if (findState != null) {
				PatternState(findState.id, stack.item, (findState.researchPercent + researchAdvance).coerceAtMost(1.0))
			} else {
				PatternState(UUID.randomUUID(), stack.item, researchAdvance)
			}

		if (!matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = true).isFailed) {
			val copy = stack.copy().also { it.count = 1 }
			stack.shrink(1)
			container.setChanged()
			val complexity = MatterManager.get(copy).complexity
			return JobContainer.success(ItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)) * MachinesConfig.MATTER_SCANNER.workTimeMultiplier, MachinesConfig.MATTER_SCANNER.energyConsumption))
		}

		return JobContainer.noItem()
	}

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


	override fun jobUpdated(new: ItemJob?, old: ItemJob?, id: Int) {
		visualItemStack = new?.itemStack ?: ItemStack.EMPTY
		visualProgress = 0f
	}

	var visualItemStack by synchronizer.item(observe = false)
		private set

	var visualProgress by synchronizer.float().property
		private set

	override fun onJobTick(status: JobStatus<ItemJob>, id: Int) {
		visualProgress = status.workProgress
	}
}
