package ru.dbotthepony.mc.otm.container

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
import it.unimi.dsi.fastutil.ints.IntSet
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.stream
import java.util.LinkedList
import java.util.stream.Stream

class CombinedContainer(containers: Stream<Pair<Container, Iterator<Int>>>) : Container {
	constructor(vararg containers: Container) : this(containers.stream().map { it to (0 until it.containerSize).iterator() })
	constructor(containers: Collection<Container>) : this(containers.stream().map { it to (0 until it.containerSize).iterator() })

	class ContainerSlot(val container: Container, val containerIndex: Int) : GetterSetter<ItemStack> {
		var item: ItemStack
			get() = container[containerIndex]
			set(value) { container[containerIndex] = value }

		override fun get(): ItemStack {
			return item
		}

		override fun accept(t: ItemStack) {
			item = t
		}

		val isEmpty: Boolean get() = item.isEmpty
	}

	private val slots: List<ContainerSlot>
	private val slotsMap: Map<Container, List<ContainerSlot>>
	private val containers: Set<Container>
	private val fullCoverage: List<Container>
	private val notFullCoverage: Map<Container, List<ContainerSlot>>

	init {
		val list = ImmutableList.Builder<ContainerSlot>()
		var i = 0
		val validationMap = Reference2ObjectOpenHashMap<Container, IntSet>()
		val slotsMap = Reference2ObjectOpenHashMap<Container, ArrayList<ContainerSlot>>()

		for ((container, slots) in containers) {
			val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntAVLTreeSet() })
			val slotList = slotsMap.computeIfAbsent(container, Object2ObjectFunction { ArrayList() })

			for (slot in slots) {
				if (validator.add(slot)) {
					i++
					val slotObj = ContainerSlot(container, slot)
					list.add(slotObj)
					slotList.add(slotObj)
				} else {
					throw IllegalArgumentException("Duplicate mapping for $container at $i for slot $slot")
				}
			}
		}

		slots = list.build()
		this.containers = ImmutableSet.copyOf(validationMap.keys)

		this.slotsMap = slotsMap.entries
			.stream()
			.map { it.key to ImmutableList.copyOf(it.value) }
			.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))

		this.fullCoverage = this.slotsMap.entries
			.stream()
			.filter { it.value.size == it.key.containerSize }
			.map { it.key }
			.collect(ImmutableList.toImmutableList())

		this.notFullCoverage = this.slotsMap.entries
			.stream()
			.filter { it.key !in this.fullCoverage }
			.collect(ImmutableMap.toImmutableMap({ it.key }, { it.value }))
	}

	override fun clearContent() {
		for (container in fullCoverage) {
			container.clearContent()
		}

		for (slots in notFullCoverage.values) {
			for (slot in slots) {
				slot.item = ItemStack.EMPTY
			}
		}
	}

	override fun getContainerSize(): Int {
		return slots.size
	}

	override fun isEmpty(): Boolean {
		if (fullCoverage.isNotEmpty())
			for (container in fullCoverage)
				if (!container.isEmpty)
					return false

		if (notFullCoverage.isNotEmpty())
			for (slots in notFullCoverage.values)
				for (slot in slots)
					if (!slot.isEmpty)
						return false

		return true
	}

	fun slotAt(index: Int): ContainerSlot {
		return slots[index]
	}

	override fun getItem(index: Int): ItemStack {
		// do not violate contract of getItem not throwing exceptions when index is invalid
		return slots.getOrNull(index)?.item ?: ItemStack.EMPTY
	}

	override fun removeItem(index: Int, count: Int): ItemStack {
		val data = slots.getOrNull(index) ?: return ItemStack.EMPTY
		return data.container.removeItem(data.containerIndex, count)
	}

	override fun removeItemNoUpdate(index: Int): ItemStack {
		val data = slots.getOrNull(index) ?: return ItemStack.EMPTY
		return data.container.removeItemNoUpdate(data.containerIndex)
	}

	override fun setItem(index: Int, value: ItemStack) {
		slots.getOrNull(index)?.item = value
	}

	override fun setChanged() {
		for (container in containers) {
			container.setChanged()
		}
	}

	fun setChanged(index: Int) {
		val data = slots.getOrNull(index) ?: return

		if (data.container is MatteryContainer) {
			data.container.setChanged(data.containerIndex)
		} else {
			data.container.setChanged()
		}
	}

	override fun stillValid(player: Player): Boolean {
		for (container in containers)
			if (!container.stillValid(player))
				return false

		return true
	}

	class Builder {
		private var built = false
		private val values = LinkedList<Pair<Container, Iterator<Int>>>()

		fun add(container: Container): Builder {
			check(!built) { "Already built!" }
			values.add(container to (0 until container.containerSize).iterator())
			return this
		}

		fun add(container: Container, slots: Iterator<Int>): Builder {
			check(!built) { "Already built!" }
			values.add(container to slots)
			return this
		}

		fun add(container: Container, slots: Iterable<Int>): Builder {
			check(!built) { "Already built!" }
			values.add(container to slots.iterator())
			return this
		}

		fun build(): CombinedContainer {
			check(!built) { "Already built!" }
			val value = CombinedContainer(values.stream())
			values.clear()
			return value
		}
	}
}
