package ru.dbotthepony.mc.otm.capability

import it.unimi.dsi.fastutil.objects.ObjectArrayList
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.neoforged.neoforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set

abstract class AbstractProfiledStorage<out P>(val parent: P) : IProfiledStorage, INBTSerializable<CompoundTag?> {
	final override val received = DecimalHistoryChart(ticks = HISTORY_SIZE)
	final override val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE)

	final override var receivedThisTick = Decimal.ZERO
		private set
	final override var transferredThisTick = Decimal.ZERO
		private set

	private var isTicking = false

	private fun startTicking() {
		if (!isTicking) {
			isTicking = true
			storages.add(this)
		}
	}

	protected fun recordTransfer(value: Decimal, simulate: Boolean): Decimal {
		if (!simulate) {
			transferredThisTick += value
			if (!value.isZero) startTicking()
		}

		return value
	}

	protected fun recordReceive(value: Decimal, simulate: Boolean): Decimal {
		if (!simulate) {
			receivedThisTick += value
			if (!value.isZero) startTicking()
		}

		return value
	}

	private fun tick(): Boolean {
		received.add(receivedThisTick)
		transferred.add(transferredThisTick)

		isTicking = !receivedThisTick.isZero || !transferredThisTick.isZero || received.any { !it.isZero } || transferred.any { !it.isZero }

		transferredThisTick = Decimal.ZERO
		receivedThisTick = Decimal.ZERO

		return isTicking
	}

	val savedata: INBTSerializable<CompoundTag?> = object : INBTSerializable<CompoundTag?> {
		override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
			return CompoundTag().also { tag ->
				tag["historyReceive"] = received.serializeNBT(registry)
				tag["historyTransfer"] = transferred.serializeNBT(registry)
			}
		}

		override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
			transferredThisTick = Decimal.ZERO
			receivedThisTick = Decimal.ZERO

			received.clear()
			transferred.clear()

			nbt ?: return

			nbt.map("historyReceive") { it: CompoundTag ->
				received.deserializeNBT(registry, it)
			}

			nbt.map("historyTransfer") { it: CompoundTag ->
				transferred.deserializeNBT(registry, it)
			}

			startTicking()
		}
	}

	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
		val tag: CompoundTag

		if (parent is INBTSerializable<*>) {
			tag = (parent as INBTSerializable<CompoundTag?>).serializeNBT(registry) ?: CompoundTag()
		} else {
			tag = CompoundTag()
		}

		val tag2 = savedata.serializeNBT(registry)!!

		for (k in tag2.allKeys) {
			tag[k] = tag2[k]!!
		}

		return tag
	}

	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
		if (parent is INBTSerializable<*>) {
			(parent as INBTSerializable<CompoundTag?>).deserializeNBT(registry, nbt)
		}

		savedata.deserializeNBT(registry, nbt)
	}

	companion object {
		const val HISTORY_SIZE = 20 * 5
		private val storages = ObjectArrayList<AbstractProfiledStorage<*>>()

		// после раздумий, наиболее корректное место для обновления состояния профилированных
		// хранилищ энергии и материи будет после всех остальных хуков на тики после тика сервера
		// разумеется, такое решение может несколько снизить производительность сервера,
		// но ничего страшного произойти не должно
		internal fun onServerPostTick() {
			storages.removeIf { !it.tick() }
		}
	}
}
