package ru.dbotthepony.mc.otm.container

import it.unimi.dsi.fastutil.ints.IntIterable
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.RecipeInput
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.isNotEmpty

interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
	fun getSlotFilter(slot: Int): Item?

	/**
	 * @return whenever the filter was set. Returns false only if container can't be filtered.
	 */
	fun setSlotFilter(slot: Int, filter: Item? = null): Boolean {
		return false
	}

	fun clearSlotFilters()

	override fun isEmpty(): Boolean
	fun setChanged(slot: Int)

	override fun size(): Int {
		return containerSize
	}

	/**
	 * Iterates over non-empty itemstacks of this container
	 */
	override fun iterator(): Iterator<ItemStack> {
		return iterator(true)
	}

	/**
	 * Iterates non-empty slots of this container
	 */
	fun slotIterator(): Iterator<IContainerSlot> {
		return slotIterator(true)
	}

	fun iterator(nonEmpty: Boolean): Iterator<ItemStack> {
		if (nonEmpty) {
			return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty }
		} else {
			return (0 until containerSize).iterator().map { this[it] }
		}
	}

	open class ContainerSlot(override val slot: Int, override val container: IMatteryContainer) : IContainerSlot {
		override val isForbiddenForAutomation: Boolean
			get() = container.isSlotForbiddenForAutomation(slot)

		override fun getFilter(): Item? {
			return container.getSlotFilter(slot)
		}

		override fun setFilter(filter: Item?): Boolean {
			return container.setSlotFilter(slot, filter)
		}

		override fun getMaxStackSize(item: ItemStack): Int {
			return container.getMaxStackSize(slot, item)
		}

		override fun setChanged() {
			container.setChanged(slot)
		}
	}

	/**
	 * Iterates either non-empty slots of container or all slots of container
	 */
	fun slotIterator(nonEmpty: Boolean): Iterator<IContainerSlot> {
		if (nonEmpty) {
			return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { containerSlot(it) }
		} else {
			return (0 until containerSize).iterator().map { containerSlot(it) }
		}
	}

	fun containerSlot(slot: Int): IContainerSlot {
		return ContainerSlot(slot, this)
	}

	fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null
	fun isSlotForbiddenForAutomation(slot: Int) = getSlotFilter(slot) === Items.AIR

	fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean {
		return testSlotFilter(slot, itemStack.item)
	}

	fun testSlotFilter(slot: Int, item: Item): Boolean {
		if (getSlotFilter(slot) == null) {
			return true
		} else {
			return getSlotFilter(slot) === item
		}
	}

	fun getMaxStackSize(slot: Int, itemStack: ItemStack) = maxStackSize.coerceAtMost(itemStack.maxStackSize)

	private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntIterable, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack {
		if (stack.isEmpty)
			return stack

		// двигаем в одинаковые слоты
		var i = slots.intIterator()

		while (i.hasNext()) {
			val slot = i.nextInt()

			if (
				(ignoreFilters || !isSlotForbiddenForAutomation(slot)) &&
				ItemStack.isSameItemSameComponents(getItem(slot), stack) &&
				(ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack))
			) {
				val slotStack = getItem(slot)
				val slotLimit = getMaxStackSize(slot, slotStack)

				if (slotStack.count < slotLimit) {
					val newCount = (slotStack.count + stack.count).coerceAtMost(slotLimit)
					val diff = newCount - slotStack.count

					if (!simulate) {
						slotStack.count = newCount
						setChanged(slot)

						if (popTime != null) {
							slotStack.popTime = popTime
						}
					}

					stack.shrink(diff)

					if (stack.isEmpty) {
						return stack
					}
				}
			}
		}

		if (!onlyIntoExisting) {
			i = slots.intIterator()

			// двигаем в пустые слоты
			while (i.hasNext()) {
				val slot = i.nextInt()

				if (
					getItem(slot).isEmpty &&
					(ignoreFilters || !isSlotForbiddenForAutomation(slot)) &&
					(ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack))
				) {
					val diff = stack.count.coerceAtMost(getMaxStackSize(slot, stack))

					if (!simulate) {
						val copyToPut = stack.copy()
						copyToPut.count = diff
						setItem(slot, copyToPut)

						if (popTime != null) {
							copyToPut.popTime = popTime
						}
					}

					stack.shrink(diff)

					if (stack.isEmpty) {
						return stack
					}
				}
			}
		}

		return stack
	}

	fun addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack {
		if (stack.isEmpty)
			return stack

		if (ignoreFilters) {
			return addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, true)
		} else {
			var copy = addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, false)
			copy = addItem(copy, simulate, false, slots, onlyIntoExisting, popTime, false)
			return copy
		}
	}

	fun handler(filter: HandlerFilter = HandlerFilter.Both): ContainerHandler {
		return ContainerHandler(this, filter)
	}

	/**
	 * Unlike [addItem], modifies original [stack]
	 *
	 * @return Whenever [stack] was modified
	 */
	fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean {
		if (stack.isEmpty)
			return false

		val result = addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters)

		if (result.count != stack.count) {
			if (!simulate) {
				stack.count = result.count
			}

			return true
		}

		return false
	}

	fun fullyAddItem(stack: ItemStack, slots: IntIterable = slotRange, ignoreFilters: Boolean = false): Boolean {
		if (!addItem(stack, true, slots, ignoreFilters).isEmpty)
			return false

		return addItem(stack, false, slots, ignoreFilters).isEmpty
	}

	fun toList(): MutableList<ItemStack> {
		val list = ArrayList<ItemStack>(size())

		for (i in 0 until size()) {
			list.add(this[i])
		}

		return list
	}

	fun shrink(slot: Int, amount: Int): Boolean {
		if (slot < 0 || slot > size())
			return false

		val item = this[slot]
		if (item.isEmpty)
			return false

		if (item.count <= amount) {
			this[slot] = ItemStack.EMPTY
		} else {
			item.shrink(amount)
			setChanged(slot)
		}

		return true
	}
}
