
@file:Suppress("unused")

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

import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.config.ConciseBalanceValues
import ru.dbotthepony.mc.otm.config.VerboseBalanceValues
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.defineDecimal
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set

sealed 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,
		direction: FlowDirection,
		maxBatteryLevel: Decimal,
		maxInput: Decimal?,
		maxOutput: Decimal?,
	) : this(listener, direction, { maxBatteryLevel }, { maxInput }, { maxOutput })

	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

		@Suppress("NAME_SHADOWING")
		var howMuch = howMuch
		val maxOutput = maxOutput

		if (maxOutput != null) {
			howMuch = howMuch.coerceAtMost(maxOutput)
		}

		if (!batteryLevel.isPositive)
			return Decimal.ZERO

		val newLevel = (batteryLevel - howMuch).moreThanZero()
		val diff = (batteryLevel - newLevel)

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

		return diff
	}

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

		@Suppress("NAME_SHADOWING")
		var howMuch = howMuch
		val maxInput = maxInput

		if (maxInput != null) {
			howMuch = howMuch.coerceAtMost(maxInput)
		}

		if (batteryLevel >= maxBatteryLevel)
			return Decimal.ZERO

		val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel)
		val diff = (newLevel - batteryLevel)

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

		return diff
	}

	override fun serializeNBT(): 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(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)
	}

	var resolver: LazyOptional<IMatteryEnergyStorage> = LazyOptional.of { this }
		private set

	fun invalidate() {
		resolver.invalidate()
	}

	fun revive() {
		resolver = LazyOptional.of { this }
	}

	companion object {
		val DEFAULT_MAX_IO = Decimal(200)
		val DEFAULT_MAX_CAPACITY = Decimal(60_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"

		fun makeConfigEntry(builder: ForgeConfigSpec.Builder, name: String? = null, capacity: Decimal = DEFAULT_MAX_CAPACITY, throughput: Decimal = DEFAULT_MAX_IO, configurator: ForgeConfigSpec.Builder.() -> Unit = {}): ConciseBalanceValues {
			if (name != null)
				builder.push(name)

			val obj = object : ConciseBalanceValues {
				override val capacity: Decimal by builder.defineDecimal("capacity", capacity, Decimal.ONE)
				override val throughput: Decimal by builder.defineDecimal("throughput", throughput, Decimal.ONE)
			}

			configurator.invoke(builder)

			if (name != null)
				builder.pop()

			return obj
		}
	}
}

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,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput })

	constructor(
		listener: () -> Unit,
		values: ConciseBalanceValues
	) : this(listener, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: BlockEntity,
		values: ConciseBalanceValues
	) : this(listener::setChanged, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: () -> Unit,
		values: VerboseBalanceValues
	) : this(listener, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		values: VerboseBalanceValues
	) : this(listener::setChanged, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput)

	companion object {
		fun appendHoverText(itemStack: ItemStack, tooltips: MutableList<Component>) {
			val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return
			val cap = WorkerEnergyStorage({}, DEFAULT_MAX_CAPACITY)
			cap.deserializeNBT(tag)
			batteryLevel(cap, tooltips, false)
		}

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

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,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput })

	constructor(
		listener: () -> Unit,
		values: ConciseBalanceValues
	) : this(listener, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: BlockEntity,
		values: ConciseBalanceValues
	) : this(listener::setChanged, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: () -> Unit,
		values: VerboseBalanceValues
	) : this(listener, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		values: VerboseBalanceValues
	) : this(listener::setChanged, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput)

	companion object {
		fun appendHoverText(itemStack: ItemStack, tooltips: MutableList<Component>) {
			val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return
			val cap = GeneratorEnergyStorage({}, DEFAULT_MAX_CAPACITY)
			cap.deserializeNBT(tag)
			batteryLevel(cap, tooltips)
		}

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

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,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput })

	constructor(
		listener: () -> Unit,
		values: ConciseBalanceValues
	) : this(listener, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: BlockEntity,
		values: ConciseBalanceValues
	) : this(listener::setChanged, values::capacity, values::throughput, values::throughput)

	constructor(
		listener: () -> Unit,
		values: VerboseBalanceValues
	) : this(listener, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		values: VerboseBalanceValues
	) : this(listener::setChanged, values::capacity, values::receive, values::extract)

	constructor(
		listener: BlockEntity,
		maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY,
		maxInput: Decimal? = DEFAULT_MAX_IO,
		maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput)

	companion object {
		fun appendHoverText(itemStack: ItemStack, tooltips: MutableList<Component>) {
			val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return
			val cap = CapacitorEnergyStorage({}, DEFAULT_MAX_CAPACITY)
			cap.deserializeNBT(tag)
			batteryLevel(cap, tooltips)
		}

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