package ru.dbotthepony.mc.otm.container

import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.tags.TagKey
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
import java.util.*
import java.util.function.Consumer

class ItemFilter(
	val size: Int,
	private val modified: Consumer<ItemFilter>? = null
) : INBTSerializable<CompoundTag?> {
	private val filter = Array<ItemStack>(size) { ItemStack.EMPTY }
	private val linkedFilter = LinkedList<ItemStack>()

	var isLocked = false

	var isWhitelist = false
		set(value) {
			if (value != field) {
				field = value
				modified?.accept(this)
			}
		}

	var matchTag = false
		set(value) {
			if (value != field) {
				field = value
				modified?.accept(this)
			}
		}

	var matchNBT = false
		set(value) {
			if (value != field) {
				field = value
				modified?.accept(this)
			}
		}

	fun clear() {
		isWhitelist = false
		matchTag = false
		matchNBT = false

		Arrays.fill(filter, ItemStack.EMPTY)
		linkedFilter.clear()

		modified?.accept(this)
	}

	operator fun set(index: Int, value: ItemStack) {
		if (value.isEmpty && filter[index].isEmpty) {
			return
		}

		val old = filter[index]
		filter[index] = value.let { if (!it.isEmpty) it.copy().also { it.count = 1 } else it }

		if (!filter[index].isEmpty && filter[index].tag != null && filter[index].tag!!.isEmpty) {
			filter[index].tag = null
		}

		if (!old.isEmpty)
			linkedFilter.remove(old)

		if (!filter[index].isEmpty)
			linkedFilter.add(filter[index])

		modified?.accept(this)
	}

	operator fun get(index: Int): ItemStack = filter[index].copy()

	fun match(value: ItemStack): Boolean {
		if (value.isEmpty) {
			return false
		}

		if (linkedFilter.isEmpty()) {
			return !isWhitelist
		}

		for (item in linkedFilter) {
			var matched = item.`is`(value.item)

			if (matched && matchTag) {
				matched = false

				val thisTags = item.tags
				val stackTags = HashSet<TagKey<Item>>()

				for (tag in value.tags) {
					stackTags.add(tag)
				}

				for (tag1 in thisTags) {
					if (stackTags.contains(tag1)) {
						matched = true
						break
					}
				}
			}

			if (matched && matchNBT) {
				val a = item.tag
				val b = value.tag

				if (a == null && b == null) {
					// nothing
				} else if (a != null && b != null) {
					matched = a == b
				} else if (a != null) {
					matched = a.isEmpty
				} else {
					matched = false
				}
			}

			if (matched) {
				return isWhitelist
			}
		}

		return !isWhitelist
	}

	override fun serializeNBT(): CompoundTag {
		return CompoundTag().also {
			it["items"] = ListTag().also {
				for (value in filter) {
					it.add(value.serializeNBT())
				}
			}

			it["is_whitelist"] = isWhitelist
			it["match_tag"] = matchTag
			it["match_nbt"] = matchNBT
		}
	}

	override fun deserializeNBT(nbt: CompoundTag?) {
		for (i in filter.indices)
			filter[i] = ItemStack.EMPTY

		if (nbt == null)
			return

		nbt.map("items") { it: ListTag ->
			for ((i, value) in it.withIndex()) {
				if (value is CompoundTag) {
					filter[i] = ItemStack.of(value)

					if (!filter[i].isEmpty) {
						linkedFilter.add(filter[i])
					}
				}
			}
		}

		isWhitelist = nbt.getBoolean("is_whitelist")
		matchTag = nbt.getBoolean("match_tag")
		matchNBT = nbt.getBoolean("match_nbt")
	}
}
