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

import com.google.common.collect.ImmutableList
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
import ru.dbotthepony.mc.otm.core.nbt.set

/**
 * Simple machine, which can work on only one job type.
 *
 * From technical point, this is a specialized use case of [MachineJobEventLoop].
 */
abstract class MatteryWorkerBlockEntity<JobType : IMachineJob>(
	type: BlockEntityType<*>,
	blockPos: BlockPos,
	blockState: BlockState,
	val jobDeserializer: (tag: CompoundTag) -> JobType?,
	maxJobs: Int = 1,
) : MatteryPoweredBlockEntity(type, blockPos, blockState) {
	val jobEventLoops: ImmutableList<MachineJobEventLoop<JobType>> = immutableList(maxJobs) { id ->
		object : MachineJobEventLoop<JobType>() {
			override val energy: IMatteryEnergyStorage?
				get() = matteryEnergy
			override val isBlockedByRedstone: Boolean
				get() = redstoneControl.isBlockedByRedstone
			override val upgrades: IMatteryUpgrade?
				get() = this@MatteryWorkerBlockEntity.upgrades

			override fun deserializeJob(nbt: CompoundTag): JobType? {
				return jobDeserializer.invoke(nbt)
			}

			override fun onJobFinish(job: JobType): JobStatus {
				return this@MatteryWorkerBlockEntity.onJobFinish(job, id)
			}

			override fun computeNextJob(): JobContainer<JobType> {
				return this@MatteryWorkerBlockEntity.computeNextJob(id)
			}

			override fun jobUpdated(new: JobType?, old: JobType?) {
				this@MatteryWorkerBlockEntity.jobUpdated(new, old, id)
			}

			override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType): JobStatus {
				return this@MatteryWorkerBlockEntity.onWorkTick(requiredPower, extractedPower, ticksAdvanced, job, id)
			}
		}
	}

	open val upgrades: IMatteryUpgrade? get() = null

	var balanceInputs = false

	init {
		savetables.bool(::balanceInputs)
	}

	protected open fun jobUpdated(new: JobType?, old: JobType?, id: Int) {}
	protected abstract fun onJobFinish(job: JobType, id: Int): JobStatus
	protected abstract fun computeNextJob(id: Int): JobContainer<JobType>

	protected open fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType, id: Int): JobStatus {
		return JobStatus.SUCCESS
	}

	override fun saveShared(nbt: CompoundTag) {
		super.saveShared(nbt)
		nbt["jobs"] = ListTag().also {
			for ((i, job) in jobEventLoops.withIndex()) {
				it.add(job.serializeNBT().also {
					it["_id"] = i
				})
			}
		}
	}

	override fun load(nbt: CompoundTag) {
		super.load(nbt)

		for (v in nbt.getCompoundList("jobs")) {
			if ("_id" in v) {
				val id = v.getInt("_id")

				if (id in jobEventLoops.indices) {
					jobEventLoops[id].deserializeNBT(v)
				}
			}
		}
	}

	override fun setChanged() {
		super.setChanged()
		jobEventLoops.forEach { it.isIdling = false }
	}

	override fun setChangedLight() {
		super.setChangedLight()
		jobEventLoops.forEach { it.isIdling = false }
	}

	protected fun energyLevelUpdated() {
		super.setChangedLight()
		jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.POWER) }
	}

	protected fun itemContainerUpdated() {
		super.setChanged()
		jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.ITEM) }
	}

	protected fun matterLevelUpdated() {
		super.setChangedLight()
		jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.MATTER) }
	}

	override fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) {
		super.redstoneStatusUpdated(newBlocked, oldBlocked)
		jobEventLoops.forEach { it.isIdling = newBlocked }
	}

	override fun tick() {
		super.tick()
		jobEventLoops.forEach { it.think() }

		if (jobEventLoops.any { it.workingTicksAnim > 20 } && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.WORKING) {
			level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS)
		} else if (jobEventLoops.any { it.errorTicksAnim > 20 } && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.ERROR) {
			level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.ERROR), Block.UPDATE_CLIENTS)
		} else if (jobEventLoops.all { it.idleTicksAnim > 20 } && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.IDLE) {
			level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS)
		}
	}

	companion object {
		fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList<Component>, flag: TooltipFlag) {
			itemStack.tag ?: return

			WorkerEnergyStorage.appendHoverText(itemStack, blockGetter, tooltips, flag)
			MatteryPoweredBlockEntity.appendHoverText(itemStack, blockGetter, tooltips, flag)
		}
	}
}
