package ru.dbotthepony.mc.otm.storage

import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.mc.otm.core.math.isPositive
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger
import java.util.*
import java.util.stream.Stream
import kotlin.collections.ArrayList

class RemoteTuple<T : StorageStack<T>>(
	override var stack: T,
	override val id: UUID,
	val parent: IStorageProvider<T>,
	val local: LocalTuple<T>
) : IStorageTuple<T>, Comparable<RemoteTuple<T>> {
	override fun compareTo(other: RemoteTuple<T>): Int {
		return parent.extractPriority.compareTo(other.parent.extractPriority)
	}

	fun extract(amount: BigInteger, simulate: Boolean): T {
		return parent.extractStack(id, amount, simulate)
	}

	override fun equals(other: Any?): Boolean {
		return other is RemoteTuple<*> && other.id == id
	}

	override fun hashCode(): Int {
		return id.hashCode()
	}
}

class LocalTuple<T : StorageStack<T>>(override var stack: T, override val id: UUID) : IStorageTuple<T> {
	val tuples = ArrayList<RemoteTuple<T>>(1)

	fun grow(amount: BigInteger) {
		stack = stack.grow(amount)
	}

	fun shrink(amount: BigInteger) {
		stack = stack.shrink(amount)
	}
}

class VirtualComponent<T : StorageStack<T>>(override val storageType: StorageStack.Type<T>) : IVirtualStorageComponent<T> {
	// удаленный UUID -> Кортеж
	private val remoteTuples = Object2ObjectOpenHashMap<UUID, RemoteTuple<T>>()

	// локальный UUID -> Локальный кортеж
	private val localTuples = Object2ObjectOpenHashMap<UUID, LocalTuple<T>>()

	// Стак -> Локальный кортеж стака
	private val item2tuple = Object2ObjectOpenCustomHashMap<T, LocalTuple<T>>(StorageStack.Companion)

	private val listeners = ObjectLinkedOpenHashSet<IStorageEventConsumer<T>>()
	private val children = ObjectLinkedOpenHashSet<IStorage<T>>()
	private val consumers = ObjectLinkedOpenHashSet<IStorageAcceptor<T>>()

	override fun add(identity: IStorage<T>) {
		require(identity.storageType == storageType) { "Attempt to add component of type ${identity.storageType} to virtual component of type $storageType" }

		if (children.add(identity)) {
			if (identity is IStorageProvider<T>) {
				identity.addListenerAndNotify(this)
			} else if (identity is IStorageEventProducer<T>) {
				identity.addListener(this)
			}

			if (identity is IStorageEventConsumer<T>) {
				addListenerAndNotify(identity)
			}

			if (identity is IStorageAcceptor<T>) {
				consumers.add(identity)
			}
		}
	}

	override fun remove(identity: IStorage<T>) {
		require(identity.storageType == storageType) { "Attempt to remove component of type ${identity.storageType} from virtual component of type $storageType" }

		if (children.remove(identity)) {
			if (identity is IStorageProvider<T>) {
				identity.removeListenerAndNotify(this)
			} else if (identity is IStorageEventProducer<T>) {
				identity.removeListener(this)
			}

			if (identity is IStorageEventConsumer<T>) {
				removeListenerAndNotify(identity)
			}

			if (identity is IStorageAcceptor<T>) {
				consumers.remove(identity)
			}
		}
	}

	override fun contains(identity: IStorage<T>) = children.contains(identity)
	override fun addListener(listener: IStorageEventConsumer<T>) = listeners.add(listener)
	override fun removeListener(listener: IStorageEventConsumer<T>) = listeners.remove(listener)

	override fun get(id: UUID): T {
		return localTuples[id]?.stack ?: this.storageType.empty
	}

	override val stacks: Stream<IStorageTuple<T>> get() {
		return ArrayList<IStorageTuple<T>>(item2tuple.size).also { it.addAll(item2tuple.values) }.stream()
	}

	override fun get(key: T): UUID? {
		return item2tuple[key]?.id
	}

	override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider<T>) {
		check(!remoteTuples.containsKey(id)) { "Already tracking tuple with id $id" }

		var local = item2tuple[stack]
		val added = local == null

		if (local == null) {
			local = LocalTuple(stack, UUID.randomUUID())
			localTuples[local.id] = local
			item2tuple[stack] = local
		} else {
			local.grow(stack.count)
		}

		val remote = RemoteTuple(stack, id, provider, local)
		local.tuples.add(remote)
		remoteTuples[id] = remote

		if (added) {
			for (listener in listeners) {
				listener.onStackAdded(local.stack, local.id, this)
			}
		} else {
			for (listener in listeners) {
				listener.onStackChanged(local.stack, local.id)
			}
		}
	}

	override fun onStackChanged(stack: T, id: UUID) {
		require(stack.count.isPositive)

		val tuple = remoteTuples[id] ?: throw IllegalStateException("No such tuple with id $id")
		val diff = stack.count - tuple.stack.count
		tuple.stack = stack
		tuple.local.grow(diff)

		for (listener in listeners) {
			listener.onStackChanged(tuple.local.stack, tuple.local.id)
		}
	}

	override fun onStackRemoved(id: UUID) {
		val remote = remoteTuples.remove(id) ?: throw IllegalStateException("No such tuple with id $id")
		val local = remote.local

		local.shrink(remote.stack.count)
		check(local.tuples.remove(remote))

		val a = local.stack.isEmpty
		val b = local.tuples.isEmpty()

		if (a || b) {
			check(a && b) { "View stack is empty, but remote tuple list is not!" }
			localTuples.remove(local.id)
			checkNotNull(item2tuple.remove(remote.stack)) { "No such stack ${remote.stack}" }

			for (listener in listeners) {
				listener.onStackRemoved(local.id)
			}
		} else {
			for (listener in listeners) {
				listener.onStackChanged(local.stack, local.id)
			}
		}
	}

	override fun insertStack(stack: T, simulate: Boolean): T {
		var leftover = stack
		val consumers = ObjectArrayList(consumers)
		consumers.sortWith(IStorageAcceptor.Companion)

		for (consumer in consumers) {
			leftover = consumer.insertStack(leftover, simulate)

			if (leftover.isEmpty) {
				return leftover
			}
		}

		return leftover
	}

	override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T {
		if (!amount.isPositive)
			return this.storageType.empty

		@Suppress("name_shadowing")
		var amount = amount
		val tuple = localTuples[id]

		if (tuple == null || amount.isZero)
			return this.storageType.empty

		if (!amount.isPositive)
			amount = tuple.stack.maxStackSize ?: tuple.stack.count

		val toExtract = tuple.stack.count.coerceAtMost(amount)
		var extracted = BigInteger.ZERO
		val copy = tuple.stack
		val tuples = ArrayList(tuple.tuples)
		tuples.sort()

		for (remote in tuples) {
			extracted += remote.extract(toExtract - extracted, simulate).count

			if (extracted >= toExtract)
				break
		}

		if (extracted.isPositive) {
			return copy.copy(extracted)
		}

		return this.storageType.empty
	}

	override fun toString(): String {
		return "VirtualComponent[$storageType; listeners: ${listeners.size}; size: ${localTuples.size}]"
	}
}
