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

import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.capabilities.Capabilities
import net.neoforged.neoforge.energy.IEnergyStorage
import ru.dbotthepony.kommons.collect.flatMap
import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.energy.CombinedProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.moveEnergy
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
import ru.dbotthepony.mc.otm.core.multiblock.IMultiblockAccess
import ru.dbotthepony.mc.otm.core.multiblock.IMultiblockListener
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities

class EnergyInterfaceBlockEntity(
	val isInput: Boolean,
	type: BlockEntityType<*>,
	blockPos: BlockPos,
	blockState: BlockState
) : MatteryDeviceBlockEntity(type, blockPos, blockState), IMultiblockListener {
	interface Target {
		val energyInterfaceTarget: ProfiledEnergyStorage<*>
	}

	private val multiblocks = LinkedHashMap<IMultiblockAccess, Listenable.L>()
	private val targets = InvalidableLazy.Impl {
		multiblocks.keys.iterator().flatMap { it.blockEntities(TARGET).iterator() }.map { it.energyInterfaceTarget }.toList()
	}

	val energy = CombinedProfiledEnergyStorage(FlowDirection.input(isInput), targets::value, { level?.otmRandom })

	override fun onAddedToMultiblock(multiblock: IMultiblockAccess) {
		check(!isRemoved) { "Block was removed" }
		check(multiblocks.put(multiblock, multiblock.onGenerationChanges(Runnable { targets.invalidate() })) == null) { "$this already tracks $multiblock" }
		targets.invalidate()
	}

	override fun onRemovedFromMultiblock(multiblock: IMultiblockAccess) {
		check(multiblocks.remove(multiblock)?.remove() != null || isRemoved) { "$this does not track $multiblock" }
		targets.invalidate()
	}

	val container = object : MatteryContainer(::markDirtyFast, CAPACITY) {
		override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
			return 1
		}
	}.also(::addDroppableContainer)

	val itemHandler = container.handler(if (isInput) HandlerFilter.Dischargeable else HandlerFilter.Chargeable)
	private val neighbours = ArrayList<CapabilityCache<IEnergyStorage>>()

	override fun setRemoved() {
		super.setRemoved()
		multiblocks.values.forEach { it.remove() }
	}

	init {
		savetables.stateful(::container, BATTERY_KEY)

		// it would cause a lot of frustration if hatches accept stuff only though one face
		exposeEnergyGlobally(energy)
		exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler)

		if (!isInput) {
			for (side in RelativeSide.entries) {
				neighbours.add(CapabilityCache(side, Capabilities.EnergyStorage.BLOCK))
			}
		}
	}

	override fun tick() {
		super.tick()
		energy.tick()

		if (!redstoneControl.isBlockedByRedstone) {
			container.forEach {
				it.getCapability(Capabilities.EnergyStorage.ITEM)?.let {
					if (isInput) {
						moveEnergy(it, energy, simulate = false)
					} else {
						moveEnergy(energy, it, simulate = false)
					}
				}
			}

			if (!isInput) {
				neighbours.forEach {
					val get = it.get()

					if (get != null) {
						moveEnergy(energy, get, simulate = false)
					}
				}
			}
		}
	}

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

	companion object {
		const val CAPACITY = 1

		val TARGET = BlockEntityTag(Target::class)

		val INPUT_TAG = BlockEntityTag(EnergyInterfaceBlockEntity::class) { it.isInput }
		val OUTPUT_TAG = BlockEntityTag(EnergyInterfaceBlockEntity::class) { !it.isInput }

		fun input(blockPos: BlockPos, blockState: BlockState): EnergyInterfaceBlockEntity {
			return EnergyInterfaceBlockEntity(true, MBlockEntities.ENERGY_INPUT_INTERFACE, blockPos, blockState)
		}

		fun output(blockPos: BlockPos, blockState: BlockState): EnergyInterfaceBlockEntity {
			return EnergyInterfaceBlockEntity(false, MBlockEntities.ENERGY_OUTPUT_INTERFACE, blockPos, blockState)
		}
	}
}
