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

import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.nbt.ListTag
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.minecraftforge.common.capabilities.ForgeCapabilities
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.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.nbt.getByteArrayList
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.util.*

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

	override val blockRotation: BlockRotation get() {
		return BlockRotation.of(blockState[EnergyCounterBlock.INPUT_DIRECTION])
	}

	private val history = Array(10 * 20) { Decimal.ZERO }
	internal var historyTick = 0

	var lastTick by synchronizer.decimal()
		internal set

	var ioLimit: Decimal? = null

	fun resetStats() {
		historyTick = 0
		lastTick = Decimal.ZERO
		passed = Decimal.ZERO
		Arrays.fill(history, Decimal.ZERO)
	}

	fun getHistory(ticks: Int): Array<Decimal> {
		require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" }

		val history = Array(ticks) { Decimal.ZERO }

		for (i in 0 until ticks) {
			var index = (historyTick - i) % this.history.size
			if (index < 0) index = this.history.size - 1
			history[i] = this.history[index]
		}

		return history
	}

	fun calcAverage(ticks: Int): Decimal {
		return sumHistory(ticks) / ticks
	}

	fun sumHistory(ticks: Int): Decimal {
		require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" }

		var value = Decimal.ZERO

		for (i in 0 until ticks) {
			var index = (historyTick - i) % history.size
			if (index < 0) index = history.size - 1
			value += history[index]
		}

		return value
	}

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

	override fun saveLevel(nbt: CompoundTag) {
		super.saveLevel(nbt)
		val list = ListTag()
		nbt[POWER_HISTORY_KEY] = list
		nbt[POWER_HISTORY_POINTER_KEY] = historyTick

		for (num in history)
			list.add(num.serializeNBT())
	}

	override fun load(nbt: CompoundTag) {
		super.load(nbt)

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

		nbt.map(POWER_HISTORY_POINTER_KEY) { it: IntTag ->
			historyTick = it.asInt
		}

		Arrays.fill(history, Decimal.ZERO)

		for ((i, bytes) in nbt.getByteArrayList(POWER_HISTORY_KEY).withIndex()) {
			history[i] = Decimal.deserializeNBT(bytes)
		}
	}

	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 front.trackEnergy()
	private val outputCapability by back.trackEnergy()

	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.orNull()

			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
					history[historyTick] += diff
				}

				return diff
			}

			return Decimal.ZERO
		}

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

			val it = outputCapability.orNull()

			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
					history[historyTick] += diff
				}

				return diff
			}

			return Decimal.ZERO
		}

		override val canSetBatteryLevel: Boolean
			get() = false

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

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

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

					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.orNull()

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

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

					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.orNull()

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

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

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

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

				return Decimal.ZERO
			}
	}

	init {
		front.Cap(ForgeCapabilities.ENERGY, energyInput)
		front.Cap(MatteryCapability.ENERGY, energyInput)

		back.Cap(ForgeCapabilities.ENERGY, energyOutput)
		back.Cap(MatteryCapability.ENERGY, energyOutput)

		if (isMekanismLoaded) {
			front.Cap(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(energyInput))
			back.Cap(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(energyOutput))
		}
	}

	override fun tick() {
		super.tick()

		lastTick = history[historyTick]
		historyTick = (historyTick + 1) % history.size
		history[historyTick] = Decimal.ZERO
	}

	fun clientTick() {
		historyTick = (historyTick + 1) % history.size
		history[historyTick] = lastTick
		passed += lastTick
	}

	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"
		const val POWER_HISTORY_KEY = "passHistory"
		const val POWER_HISTORY_POINTER_KEY = "passHistoryPointer"
	}
}
