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

import com.google.common.collect.Streams
import javax.annotation.ParametersAreNonnullByDefault
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.MethodsReturnNonnullByDefault
import net.minecraft.server.level.ServerLevel
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.core.collect.iterator
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.graph.matter.MatterGraph
import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.util.ArrayList
import java.util.stream.Stream

@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
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(this::setChanged, 8) {
		override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
			if (!ItemStack.isSameItemSameTags(new, old)) {
				if (!old.isEmpty) {
					old.getCapability(MatteryCapability.PATTERN).ifPresent { cap: IPatternStorage ->
						cap.patterns.forEach { matterNode.graph.onPatternRemoved(it) }
					}
				}

				if (!new.isEmpty) {
					new.getCapability(MatteryCapability.PATTERN).ifPresent { 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).isPresent
			)
		}

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

	val itemConfig = ConfigurableItemHandler(inputOutput = container.handler { _: Int, stack: ItemStack -> stack.getCapability(MatteryCapability.PATTERN).isPresent })

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

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

	init {
		exposeGlobally(MatteryCapability.PATTERN, 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<out IPatternState> get() {
		val streams = ArrayList<Stream<out IPatternState>>()

		for (provider in this.container.iterator(MatteryCapability.PATTERN)) {
			streams.add(provider.second.patterns)
		}

		return Streams.concat(*streams.toTypedArray())
	}

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

		for ((_, pattern) in this.container.iterator(MatteryCapability.PATTERN))
			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(MatteryCapability.PATTERN))
			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: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus {
		for (pair in this.container.iterator(MatteryCapability.PATTERN)) {
			val status = pair.second.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
	}
}
