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 net.minecraftforge.common.ForgeConfigSpec
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.MachineJobEventLoop
import ru.dbotthepony.mc.otm.config.ConciseBalanceValues
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
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.IPatternState
import ru.dbotthepony.mc.otm.capability.matter.PatternState
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.core.math.DecimalConfigValue
import ru.dbotthepony.mc.otm.core.math.defineDecimal
import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.core.util.WriteOnce
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<MachineItemJob>(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ::MachineItemJob) {

	val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer)
	val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, ENERGY_VALUES))

	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: IPatternState) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN)
		}

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

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

	init {
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)

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

	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(job: MachineItemJob, id: Int): JobStatus {
		val stack = job.itemStack
		if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return JobStatus.SUCCESS

		var findState: IPatternState? = 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: IPatternState =
			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) {
			return JobStatus.SUCCESS
		} else {
			return JobStatus.FAILURE_WAIT
		}
	}

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

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

		var findState: IPatternState? = 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: IPatternState =
			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(MachineItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)), BASE_CONSUMPTION))
		}

		return JobContainer.noItem()
	}

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


	override fun jobUpdated(new: MachineItemJob?, old: MachineItemJob?, 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 onWorkTick(
		requiredPower: Decimal,
		extractedPower: Decimal,
		ticksAdvanced: Double,
		job: MachineItemJob,
		id: Int
	): JobStatus {
		val result = super.onWorkTick(requiredPower, extractedPower, ticksAdvanced, job, id)

		visualProgress = jobEventLoops[0].workProgress

		return result
	}

	companion object {
		private val BASE_CONSUMPTION get() = _BASE_CONSUMPTION.get()

		private var _BASE_CONSUMPTION: DecimalConfigValue by WriteOnce()
		var ENERGY_VALUES: ConciseBalanceValues by WriteOnce()
			private set

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

			ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(80_000), throughput = Decimal(400))
			_BASE_CONSUMPTION = builder.defineDecimal("baseConsumption", Decimal(40), Decimal.ONE)

			builder.pop()
		}
	}
}
