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

import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.ticks.ContainerSingleItem
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.extractEnergy
import ru.dbotthepony.mc.otm.capability.receiveEnergy
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.nbt.getItemStack
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
import ru.dbotthepony.mc.otm.registry.StatNames
import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger
import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger

class BatteryBackedEnergyStorage(
	private val ply: Player,
	synchronizer: FieldSynchronizer,
	initialCharge: Decimal,
	maxCharge: Decimal,
	val isAndroid: Boolean,
	val onChange: Runnable? = null
) : IMatteryEnergyStorage, INBTSerializable<CompoundTag?>, ContainerSingleItem {
	override val energyFlow: FlowDirection
		get() = FlowDirection.INPUT

	private var battery by synchronizer.decimal(initialCharge)
	private var maxBattery by synchronizer.decimal(maxCharge)

	var item by synchronizer.item(setter = setter@{ value, access, _ ->
		access.write(value)

		if (ply is ServerPlayer && isAndroid) {
			AndroidBatteryTrigger.trigger(ply, value)
		} else if (ply is ServerPlayer && !isAndroid) {
			ExopackBatterySlotTrigger.trigger(ply, value)
		}

		onChange?.run()
	})

	override fun getItem(slot: Int): ItemStack {
		require(slot == 0) { "Invalid slot $slot" }
		return item
	}

	override fun removeItem(slot: Int, count: Int): ItemStack {
		require(slot == 0) { "Invalid slot $slot" }
		return item.split(count)
	}

	override fun setItem(slot: Int, stack: ItemStack) {
		require(slot == 0) { "Invalid slot $slot" }
		item = stack
	}

	override fun setChanged() {

	}

	override fun stillValid(p_18946_: Player): Boolean {
		return true
	}

	override fun serializeNBT(): CompoundTag {
		return CompoundTag().also {
			it["battery"] = battery.serializeNBT()
			it["maxBattery"] = maxBattery.serializeNBT()
			it["item"] = item.serializeNBT()
		}
	}

	override fun deserializeNBT(tag: CompoundTag?) {
		tag ?: return
		battery = tag.getDecimal("battery")
		maxBattery = tag.getDecimal("maxBattery")
		item = tag.getItemStack("item")
	}

	fun tick() {
		if (!item.isEmpty && battery < maxBattery) {
			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
				battery += it.extractEnergy(maxBattery - battery, false)
			}
		}
	}

	override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		@Suppress("name_shadowing")
		var howMuch = howMuch
		var drained = Decimal.ZERO

		if (!item.isEmpty) {
			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
				val extracted = it.extractEnergy(howMuch, simulate)
				drained += extracted
				howMuch -= extracted

				if (howMuch.isZero) {
					if (!simulate && ply is ServerPlayer && isAndroid) {
						ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10)
					}

					onChange?.run()
					return drained
				}
			}
		}

		val new = (battery - howMuch).moreThanZero()
		drained += battery - new

		if (!simulate) {
			battery = new

			if (ply is ServerPlayer && isAndroid) {
				ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10)
			}

			onChange?.run()
		}

		return drained
	}

	override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
		@Suppress("name_shadowing")
		var howMuch = howMuch
		var received = Decimal.ZERO

		if (!item.isEmpty) {
			item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
				val extracted = it.receiveEnergy(howMuch, simulate)
				received += extracted
				howMuch -= extracted
			}

			if (howMuch.isZero) {
				onChange?.run()
				return received
			}
		}

		val new = (battery + howMuch).coerceAtMost(maxBattery)
		received += new - battery

		if (!simulate) {
			battery = new
			onChange?.run()
		}

		return received
	}

	override var batteryLevel: Decimal
		get() {
			if (!item.isEmpty) {
				item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
					if (it is IMatteryEnergyStorage) {
						return battery + it.batteryLevel
					} else {
						return battery + it.energyStored
					}
				}
			}

			return battery
		}
		set(value) {
			battery = value
			onChange?.run()
		}

	override var maxBatteryLevel: Decimal
		get() {
			if (item != ItemStack.EMPTY) {
				item.getCapability(ForgeCapabilities.ENERGY).ifPresentK {
					if (it is IMatteryEnergyStorage) {
						return maxBattery + it.maxBatteryLevel
					} else {
						return maxBattery + it.maxEnergyStored
					}
				}
			}

			return maxBattery
		}
		set(value) {
			maxBattery = value
			onChange?.run()
		}
}
