
@file:Suppress("unused")

package ru.dbotthepony.mc.otm.capability.energy

import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.neoforged.neoforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set

open class BlockEnergyStorageImpl(
	protected val listener: () -> Unit,
	final override val energyFlow: FlowDirection,
	private val maxBatteryLevelProvider: () -> Decimal,
	private val maxInputProvider: () -> Decimal?,
	private val maxOutputProvider: () -> Decimal?,
) : IMatteryEnergyStorage, INBTSerializable<CompoundTag?>, IEnergyStorageImpl {
	constructor(
		listener: () -> Unit,
		energyFlow: FlowDirection,
		values: EnergyBalanceValues
	) : this(listener, energyFlow, values::energyCapacity, values::energyThroughput, values::energyThroughput)

	constructor(
		listener: () -> Unit,
		energyFlow: FlowDirection,
		values: VerboseEnergyBalanceValues
	) : this(listener, energyFlow, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract)

	private var maxInputStorage: Decimal? = null
	private var maxOutputStorage: Decimal? = null
	private var maxBatteryLevelStorage: Decimal? = null

	final override var maxInput: Decimal?
		get() = maxInputStorage ?: maxInputProvider.invoke()
		protected set(value) {
			if (value != maxInputStorage) {
				maxInputStorage = value
				listener.invoke()
			}
		}

	final override var maxOutput: Decimal?
		get() = maxOutputStorage ?: maxOutputProvider.invoke()
		protected set(value) {
			if (value != maxOutputStorage) {
				maxOutputStorage = value
				listener.invoke()
			}
		}

	override var maxBatteryLevel: Decimal
		get() = maxBatteryLevelStorage ?: maxBatteryLevelProvider.invoke()
		protected set(value) {
			if (value != maxBatteryLevelStorage) {
				maxBatteryLevelStorage = value
				listener.invoke()
			}
		}

	override var batteryLevel = Decimal.ZERO
		set(value) {
			if (value != field) {
				field = value
				listener.invoke()
			}
		}

	override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive)
			return Decimal.ZERO

		if (!batteryLevel.isPositive)
			return Decimal.ZERO

		val newLevel = (batteryLevel - howMuch.coerceAtMost(maxOutput ?: Decimal.POSITIVE_INFINITY)).moreThanZero()
		val diff = (batteryLevel - newLevel).coerceIn(Decimal.ZERO, howMuch)

		if (!simulate && batteryLevel != newLevel) {
			batteryLevel = newLevel
		}

		return diff
	}

	override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive)
			return Decimal.ZERO

		if (batteryLevel >= maxBatteryLevel && !maxBatteryLevel.isInfinite)
			return Decimal.ZERO

		val newLevel = (batteryLevel + howMuch.coerceAtMost(maxInput ?: Decimal.POSITIVE_INFINITY)).coerceAtMost(maxBatteryLevel)
		val diff = (newLevel - batteryLevel).coerceIn(Decimal.ZERO, howMuch)

		if (!simulate && batteryLevel != newLevel) {
			batteryLevel = newLevel
		}

		return diff
	}

	override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
		val tag = CompoundTag()
		tag[ENERGY_STORED_KEY] = batteryLevel.serializeNBT()

		maxBatteryLevelStorage?.let { tag[ENERGY_STORED_MAX_KEY] = it.serializeNBT() }
		maxInputStorage?.let { tag[MAX_INPUT_KEY] = it.serializeNBT() }
		maxOutputStorage?.let { tag[MAX_OUTPUT_KEY] = it.serializeNBT() }

		return tag
	}

	override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
		if (nbt == null) return
		batteryLevel = nbt.mapPresent(ENERGY_STORED_KEY, Decimal.Companion::deserializeNBT) ?: Decimal.ZERO
		maxBatteryLevelStorage = nbt.mapPresent(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT)
		maxInputStorage = nbt.mapPresent(MAX_INPUT_KEY, Decimal.Companion::deserializeNBT)
		maxOutputStorage = nbt.mapPresent(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT)
	}

	companion object {
		val DEFAULT_MAX_IO = Decimal(400)
		val DEFAULT_MAX_CAPACITY = Decimal(40_000)

		const val ENERGY_STORED_KEY = "energy_stored"
		const val ENERGY_STORED_MAX_KEY = "energy_stored_max"
		const val MAX_INPUT_KEY = "max_input"
		const val MAX_OUTPUT_KEY = "max_output"
	}
}

open class WorkerEnergyStorage(
	listener: () -> Unit,
	maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY },
	maxInput: () -> Decimal? = { DEFAULT_MAX_IO },
	maxOutput: () -> Decimal? = maxInput
) : BlockEnergyStorageImpl(listener, FlowDirection.INPUT, maxBatteryLevel, maxInput, maxOutput) {
	constructor(
		listener: () -> Unit,
		values: EnergyBalanceValues
	) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput)

	constructor(
		listener: () -> Unit,
		values: VerboseEnergyBalanceValues
	) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract)
}

open class GeneratorEnergyStorage(
	listener: () -> Unit,
	maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY },
	maxInput: () -> Decimal? = { DEFAULT_MAX_IO },
	maxOutput: () -> Decimal? = maxInput
) : BlockEnergyStorageImpl(listener, FlowDirection.OUTPUT, maxBatteryLevel, maxInput, maxOutput) {
	constructor(
		listener: () -> Unit,
		values: EnergyBalanceValues
	) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput)

	constructor(
		listener: () -> Unit,
		values: VerboseEnergyBalanceValues
	) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract)
}

open class CapacitorEnergyStorage(
	listener: () -> Unit,
	maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY },
	maxInput: () -> Decimal? = { DEFAULT_MAX_IO },
	maxOutput: () -> Decimal? = maxInput
) : BlockEnergyStorageImpl(listener, FlowDirection.BI_DIRECTIONAL, maxBatteryLevel, maxInput, maxOutput) {
	constructor(
		listener: () -> Unit,
		values: EnergyBalanceValues
	) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput)

	constructor(
		listener: () -> Unit,
		values: VerboseEnergyBalanceValues
	) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract)
}
