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

import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.container.MatteryContainer
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.block.matter.PatternStorageBlock
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import ru.dbotthepony.mc.otm.menu.matter.PatternStorageMenu
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.matter.*
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.core.collect.filterNotNull
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.filterNotNull
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import java.util.stream.Stream

class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
	MatteryDeviceBlockEntity(MBlockEntities.PATTERN_STORAGE, p_155229_, p_155230_), IPatternStorage {

	val matterNode = SimpleMatterNode(patterns = this)

	val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 8) {
		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
			if (!ItemStack.isSameItemSameComponents(new, old)) {
				if (!old.isEmpty) {
					old.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage ->
						cap.patterns.forEach { matterNode.graph.onPatternRemoved(it) }
					}
				}

				if (!new.isEmpty) {
					new.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage ->
						cap.patterns.forEach { matterNode.graph.onPatternAdded(it) }
					}
				}

				updateBlockstate()
			}

			super.setChanged(slot, new, old)
		}

		override fun getMaxStackSize(): Int = 1
	}.also(::addDroppableContainer)

	private fun updateBlockstate() {
		val level = level ?: return

		var state = blockState

		for (i in 0..7) {
			state = state.setValue(
				PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[i],
				this.container.getItem(i).getCapability(MatteryCapability.PATTERN_ITEM) != null
			)
		}

		if (state !== blockState) {
			level.setBlock(blockPos, state, Block.UPDATE_CLIENTS)
		}
	}

	val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(HandlerFilter.IsPattern.and(HandlerFilter.OnlyIn)))

	override fun setLevel(level: Level) {
		super.setLevel(level)
		matterNode.discover(this)
	}

	init {
		exposeGlobally(MatteryCapability.PATTERN_BLOCK, this)
		exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)

		savetables.stateful(::container, INVENTORY_KEY)
	}

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

	override val patterns: Stream<PatternState> get() {
		return container.stream()
			.filter { it.isNotEmpty }
			.map { it.getCapability(MatteryCapability.PATTERN_ITEM) }
			.filterNotNull()
			.flatMap { it.patterns }
	}

	override val patternCapacity: Int get() {
		var stored = 0L

		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull())
			stored += pattern.patternCapacity.toLong()

		return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
	}

	override val storedPatterns: Int get() {
		var stored = 0L

		for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull())
			stored += pattern.storedPatterns.toLong()

		return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
	}

	override fun setRemoved() {
		super.setRemoved()
		matterNode.isValid = false
	}

	override fun insertPattern(pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus {
		for (spattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull()) {
			val status = spattern.insertPattern(pattern, onlyUpdate, simulate)

			if (!status.isFailed) {
				if (!simulate) {
					setChanged()

					if (status.isInserted) {
						matterNode.graph.onPatternAdded(status.newState!!)
					} else {
						matterNode.graph.onPatternUpdated(status.newState!!, status.oldState!!)
					}
				}

				return status
			}
		}

		return PatternInsertFailure
	}
}
