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

import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Level
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded
import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull

abstract class ItemEnergyStorageImpl(val itemStack: ItemStack) : IMatteryEnergyStorage, ICapabilityProvider, IEnergyStorageImpl {
	private val resolver = LazyOptional.of { this }
	private val resolverMekanism = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(this) } else null

	override fun <T : Any> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
		if (cap === ForgeCapabilities.ENERGY || cap === MatteryCapability.ENERGY) {
			return resolver.cast()
		} else if (cap === MatteryCapability.MEKANISM_ENERGY) {
			return resolverMekanism?.cast() ?: LazyOptional.empty()
		}

		return LazyOptional.empty()
	}

	abstract val initialBatteryLevel: Decimal

	override var batteryLevel: Decimal
		get() = itemStack.tag?.map(ENERGY_KEY, Decimal.Companion::deserializeNBT) ?: initialBatteryLevel
		set(value) { itemStack.tagNotNull[ENERGY_KEY] = value.serializeNBT() }

	override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive || itemStack.count != 1)
			return Decimal.ZERO

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

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

		val batteryLevel = batteryLevel

		if (!batteryLevel.isPositive)
			return Decimal.ZERO

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

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

		return diff
	}

	override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		if (!howMuch.isPositive || itemStack.count != 1)
			return Decimal.ZERO

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

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

		val batteryLevel = batteryLevel

		if (batteryLevel >= maxBatteryLevel)
			return Decimal.ZERO

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

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

		return diff
	}

	companion object {
		const val ENERGY_KEY = "energy"

		fun appendHoverText(itemStack: ItemStack, level: Level?, tooltips: MutableList<Component>, flag: TooltipFlag) {
			appendHoverText(itemStack, tooltips)
		}

		fun appendHoverText(itemStack: ItemStack, tooltips: MutableList<Component>) {
			val energy = itemStack.energy ?: return

			if (energy is IMatteryEnergyStorage) {
				batteryLevel(energy, tooltips)
			} else {
				batteryLevel(energy, tooltips)
			}
		}
	}
}

abstract class SimpleEnergyItem(
	override val energyFlow: FlowDirection,
	stack: ItemStack,
	maxBatteryLevel: Decimal,
	maxInput: Decimal?,
	maxOutput: Decimal?,
	override val initialBatteryLevel: Decimal
) : ItemEnergyStorageImpl(stack) {
	override var maxInput: Decimal? = maxInput
		protected set

	override var maxOutput: Decimal? = maxOutput
		protected set

	override var maxBatteryLevel: Decimal = maxBatteryLevel
		protected set
}

open class EnergyConsumerItem(
	stack: ItemStack,
	maxBatteryLevel: Decimal,
	maxInput: Decimal? = null,
	maxOutput: Decimal? = maxInput,
	initialBatteryLevel: Decimal = Decimal.ZERO
) : SimpleEnergyItem(FlowDirection.INPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel)

open class EnergyProducerItem(
	stack: ItemStack,
	maxBatteryLevel: Decimal,
	maxInput: Decimal? = null,
	maxOutput: Decimal? = maxInput,
	initialBatteryLevel: Decimal = Decimal.ZERO
) : SimpleEnergyItem(FlowDirection.OUTPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel)

open class EnergyCapacitorItem(
	stack: ItemStack,
	maxBatteryLevel: Decimal,
	maxInput: Decimal? = null,
	maxOutput: Decimal? = maxInput,
	initialBatteryLevel: Decimal = Decimal.ZERO
) : SimpleEnergyItem(FlowDirection.BI_DIRECTIONAL, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel)

