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.ItemJob
import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
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.*
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.container.UpgradeContainer
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.data.UUIDCodec
import ru.dbotthepony.mc.otm.data.minRange
import ru.dbotthepony.mc.otm.graph.matter.MatterNode
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MItems
import java.util.*

class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
	MatteryWorkerBlockEntity<MatterReplicatorBlockEntity.ReplicatorJob>(MBlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_, ReplicatorJob.CODEC) {

	class ReplicatorJob(
		itemStack: ItemStack,
		val matterPerTick: Decimal,
		val task: UUID,
		var matterValue: Decimal,
		val pattern: Optional<PatternState>,
		val asDust: Boolean,
		ticks: Double,
	) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_REPLICATOR.energyConsumption) {
		companion object {
			val CODEC: Codec<ReplicatorJob> by lazy {
				RecordCodecBuilder.create {
					it.group(
						ItemStack.CODEC.fieldOf("Item").forGetter(ReplicatorJob::itemStack),
						DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterPerTick").forGetter(ReplicatorJob::matterPerTick),
						UUIDCodec.fieldOf("task").forGetter(ReplicatorJob::task),
						DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(ReplicatorJob::matterValue),
						PatternState.CODEC.optionalFieldOf("pattern").forGetter(ReplicatorJob::pattern),
						Codec.BOOL.fieldOf("asDust").forGetter(ReplicatorJob::asDust),
						Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("Ticks").forGetter(ReplicatorJob::ticks),
					).apply(it, ::ReplicatorJob)
				}
			}
		}
	}

	override val upgrades = UpgradeContainer(this::markDirtyFast, 3, UpgradeType.REPLICATOR)
	override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_REPLICATOR)))
	val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_REPLICATOR::matterCapacity)))
	val outputContainer = MatteryContainer(::itemContainerUpdated, 3).also(::addDroppableContainer)
	val dustContainer = MatteryContainer(::itemContainerUpdated, 2).also(::addDroppableContainer)

	val energyConfig = ConfigurableEnergy(energy)
	val itemConfig = ConfigurableItemHandler(output = CombinedItemHandler(outputContainer.handler(HandlerFilter.OnlyOut), dustContainer.handler(HandlerFilter.OnlyOut)))

	val matterNode = object : MatterNode() {
		override fun getMatterHandler(): IMatterStorage {
			return matter
		}

		override fun onMatterTaskCreated(task: ReplicationTask) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.OBSERVING)
		}

		override fun onMatterTaskUpdated(newState: ReplicationTask, oldState: ReplicationTask) {
			jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.OBSERVING)
		}

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

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

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

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

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

		if (job.asDust) {
			job.matterValue = MItems.MATTER_DUST.moveIntoContainer(job.matterValue, dustContainer, 0, 1)

			if (!job.matterValue.isZero) {
				return status.throttle()
			}

			matterNode.graph.notifyTaskCompletion(job.task)
		} else {
			if (!outputContainer.fullyAddItem(job.itemStack)) {
				return status.noItem()
			}

			matterNode.graph.notifyTaskCompletion(job.task)
		}
	}

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

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

	override fun jobUpdated(new: ReplicatorJob?, old: ReplicatorJob?, 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

	var renderRotation = 0f
	var lastRender = 0L
	var particleRenderScore = 0L

	override fun tick() {
		super.tick()

		if (jobEventLoops[0].currentJob == null && matter.storedMatter.isPositive) {
			matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false)
		}
	}

	override fun computeNextJob(id: Int): JobContainer<ReplicatorJob> {
		if (energy.batteryLevel < MachinesConfig.MATTER_REPLICATOR.energyConsumption) {
			return JobContainer.noEnergy()
		}

		val allocation = matterNode.graph.allocateTask(simulate = false) ?: return JobContainer.observe()
		val stack = allocation.task.stack(1)
		val matter = MatterManager.get(stack)

		// ????????
		if (!matter.hasMatterValue) return JobContainer.failure()

		val ticks = matter.complexity

		return JobContainer.success(ReplicatorJob(
			itemStack = stack,
			matterPerTick = matter.matter / ticks,
			task = allocation.task.id,
			matterValue = matter.matter,
			pattern = Optional.ofNullable(allocation.pattern),
			asDust = (level?.random?.nextDouble() ?: 1.0) * upgrades.failureMultiplier > (allocation.pattern?.researchPercent ?: 2.0),
			ticks = ticks,
		))
	}

	override fun onJobTick(status: JobStatus<ReplicatorJob>, id: Int) {
		val job = status.job
		val drainPerTick = job.matterPerTick * status.ticksAdvanced

		if (matter.extractMatter(drainPerTick, true) < drainPerTick) {
			val toDrain = (drainPerTick * Decimal(200))
				.coerceAtMost(job.matterPerTick * (status.ticks - status.workTicks + status.ticksAdvanced))
				.coerceAtLeast(Decimal.ONE)
				.coerceAtMost(matter.missingMatter)

			matter.receiveMatter(matterNode.graph.extractMatter(toDrain, false), false)
		}

		status.scale(matter.extractMatter(drainPerTick, false) / drainPerTick)
		visualProgress = status.workProgress
	}
}
