package ru.dbotthepony.mc.otm.block.entity.tech

import com.google.common.collect.ImmutableList
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.capabilities.Capabilities
import ru.dbotthepony.kommons.util.DelegateSetter
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.*
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.countingLazy
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities

class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ENERGY_COUNTER, p_155229_, p_155230_) {
	var passed by syncher.decimal()

	override val blockRotation: BlockRotation by countingLazy(blockStateChangesCounter) {
		BlockRotation.of(blockState[EnergyCounterBlock.INPUT_DIRECTION])
	}

	val history5s = DecimalHistoryChart(1, 100)
	val history15s = DecimalHistoryChart(3, 100)
	val history1m = DecimalHistoryChart(12, 100)
	val history10m = DecimalHistoryChart(120, 100)
	val history1h = DecimalHistoryChart(720, 100)
	val history6h = DecimalHistoryChart(720 * 6, 100)
	val history24h = DecimalHistoryChart(720 * 24, 100)

	val charts: ImmutableList<DecimalHistoryChart> = ImmutableList.of(
		history5s, history15s, history1m, history10m, history1h, history6h, history24h,
	)

	init {
		charts.forEach { syncher.add(it) }
	}

	private var thisTick = Decimal.ZERO
	var lastTick by syncher.decimal()
		internal set

	var displayChartOnBlock by syncher.int(0, setter = DelegateSetter { field, value ->
		if (value in 0 .. charts.size + 1) {
			field.accept(value)
			markDirtyFast()
		}
	})

	var ioLimit: Decimal? = null

	fun resetStats() {
		charts.forEach { it.clear() }
		lastTick = Decimal.ZERO
		passed = Decimal.ZERO
	}

	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.saveShared(nbt, registry)
		nbt[PASSED_ENERGY_KEY] = passed.serializeNBT()
		ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() }
	}

	init {
		savetablesConfig.int(::displayChartOnBlock)
		savetables.stateful(::history5s)
		savetables.stateful(::history15s)
		savetables.stateful(::history1m)
		savetables.stateful(::history10m)
		savetables.stateful(::history1h)
		savetables.stateful(::history6h)
		savetables.stateful(::history24h)
	}

	override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.saveLevel(nbt, registry)
	}

	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.loadAdditional(nbt, registry)

		passed = nbt.getDecimal(PASSED_ENERGY_KEY)
		ioLimit = nbt.mapPresent(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT)
	}

	override val defaultDisplayName: Component
		get() = MACHINE_NAME

	override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
		return EnergyCounterMenu(containerID, inventory, this)
	}

	private val energyInput = EnergyCounterCap(true)
	private val energyOutput = EnergyCounterCap(false)

	private val inputCapability by CapabilityCache(RelativeSide.FRONT, Capabilities.EnergyStorage.BLOCK)
	private val outputCapability by CapabilityCache(RelativeSide.BACK, Capabilities.EnergyStorage.BLOCK)

	private inner class EnergyCounterCap(isInput: Boolean) : IMatteryEnergyStorage {
		override val energyFlow = FlowDirection.input(isInput)

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

			val it = inputCapability

			if (it != null) {
				val diff: Decimal

				val ioLimit = ioLimit

				if (ioLimit != null) {
					diff = it.extractEnergy(howMuch.coerceAtMost(ioLimit), simulate)
				} else {
					diff = it.extractEnergy(howMuch, simulate)
				}

				if (!simulate) {
					passed += diff
					thisTick += diff
				}

				return diff
			}

			return Decimal.ZERO
		}

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

			val it = outputCapability

			if (it != null) {
				val diff: Decimal

				val ioLimit = ioLimit

				if (ioLimit != null) {
					diff = it.receiveEnergy(howMuch.coerceAtMost(ioLimit), simulate)
				} else {
					diff = it.receiveEnergy(howMuch, simulate)
				}

				if (!simulate) {
					passed += diff
					thisTick += diff
				}

				return diff
			}

			return Decimal.ZERO
		}

		override val canSetBatteryLevel: Boolean
			get() = false

		override var batteryLevel: Decimal
			get() {
				if (energyFlow.input) {
					val it = outputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.batteryLevel
						}

						return Decimal(it.energyStored)
					}
				} else {
					val it = inputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.batteryLevel
						}

						return Decimal(it.energyStored)
					}
				}

				return Decimal.ZERO
			}
			set(value) {
				throw UnsupportedOperationException("Can't set energy on energy counter")
			}

		override val maxBatteryLevel: Decimal
			get() {
				if (energyFlow.input) {
					val it = outputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.maxBatteryLevel
						}

						return Decimal(it.maxEnergyStored)
					}
				} else {
					val it = inputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.maxBatteryLevel
						}

						return Decimal(it.maxEnergyStored)
					}
				}

				return Decimal.ZERO
			}

		override val missingPower: Decimal
			get() {
				if (energyFlow.input) {
					val it = outputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.missingPower
						}

						return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0))
					}
				} else {
					val it = inputCapability

					if (it != null) {
						if (it is IMatteryEnergyStorage) {
							return it.missingPower
						}

						return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0))
					}
				}

				return Decimal.ZERO
			}
	}

	init {
		exposeEnergySided(RelativeSide.FRONT, energyInput)
		exposeEnergySided(RelativeSide.BACK, energyOutput)
	}

	override fun tick() {
		super.tick()

		lastTick = thisTick
		charts.forEach { it.add(thisTick) }
		thisTick = Decimal.ZERO
	}

	companion object {
		private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.energy_counter")
		const val PASSED_ENERGY_KEY = "passedEnergy"
		const val IO_LIMIT_KEY = "IOLimit"
	}
}
