package ru.dbotthepony.mc.otm.core.collect

import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.nbt.contains
import java.util.UUID

/**
 * This class represents an `UUID -> Int` map, with each `Int` value being added to [value] property
 *
 * This is like Minecraft's Attribute map, except it operate only on whole numbers
 */
class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap<UUID, Int> = HashMap()) : INBTSerializable<ListTag?> {
	var value: Int = 0
		private set

	fun recompute() {
		var value = 0

		for (mapValue in backingMap.values) {
			value += mapValue
		}

		if (this.value != value) {
			this.value = value
			observer.invoke(value)
		}
	}

	operator fun set(key: UUID, value: Int): Boolean {
		val old = backingMap.put(key, value)

		if (old == value) {
			return false
		}

		this.value += value - (old ?: 0)
		observer.invoke(this.value)
		return true
	}

	operator fun get(key: UUID): Int? {
		return backingMap[key]
	}

	operator fun contains(key: UUID): Boolean {
		return backingMap.containsKey(key)
	}

	fun remove(key: UUID): Boolean {
		if (!backingMap.containsKey(key)) {
			return false
		}

		val old = backingMap.remove(key) ?: return true
		value -= old
		return true
	}

	fun clear() {
		backingMap.clear()
		val old = this.value
		this.value = 0
		if (old != this.value) {
			observer.invoke(this.value)
		}
	}

	override fun serializeNBT(): ListTag {
		return ListTag().also {
			for ((key, value) in backingMap) {
				it.add(CompoundTag().also {
					it.putUUID("key", key)
					it.putInt("value", value)
				})
			}
		}
	}

	override fun deserializeNBT(nbt: ListTag?) {
		backingMap.clear()
		nbt ?: return
		val old = this.value
		this.value = 0

		for (value in nbt) {
			value as CompoundTag

			if (value.contains("key", "value")) {
				val int = value.getInt("value")
				backingMap.put(value.getUUID("key"), int)
				this.value += int
			}
		}

		if (old != this.value) {
			observer.invoke(this.value)
		}
	}
}
