package ru.dbotthepony.mc.otm.storage

import java.math.BigInteger
import java.util.*
import java.util.stream.Stream

/**
 * Represents a stack in storage system.
 *
 * In general there are next rules:
 * 1. Once created, stack is immutable, except for it's [count], therefore [copy] is expected to do
 * "shallow" copies (not performance taxing).
 * 2. Due to condition above, and due to sublying storage most of time being
 * mutable, it is expected you do defensive copies. Examples of when you should do
 * them are described on related interfaces.
 * 3. For storing stacks as [Map] keys, a stack with size of 1 is utilized (requiring [equals] and [hashCode] to return meaningful results, see [IStorageStack.key]).
 * 4. For equality (INCLUDING count), regular [equals] is utilized.
 */
interface IStorageStack {
	fun copy(): IStorageStack

	/**
	 * Size of this storage stack
	 *
	 * This is overriden in subclasses
	 */
	var count: BigInteger
	val isEmpty: Boolean

	/**
	 * @return max stack size for this stack object,
	 * null if unlimited (default)
	 */
	val maxStackSize: BigInteger? get() = null

	/**
	 * Increase [count] by [amount]
	 */
	fun grow(amount: BigInteger) {
		count += amount
	}

	/**
	 * Decrease [count] by [amount]
	 */
	fun shrink(amount: BigInteger) {
		count -= amount
	}
}

@Suppress("UNCHECKED_CAST")
fun <T : IStorageStack> T.key(): T {
	return copy().also { it.count = BigInteger.ONE } as T
}

/**
 * Storage system root, along IStorageStack interface
 */
interface IStorage<T : IStorageStack> {
	/**
	 * @return Identity of this virtual component
	 */
	val storageType: StorageStackType<T>
}

/**
 * Generates events for [IStorageEventConsumer]
 */
interface IStorageEventProducer<T : IStorageStack> : IStorage<T> {
	/**
	 * [listener] is [IStorageEventConsumer] which want to subscribe to our events
	 */
	fun addListener(listener: IStorageEventConsumer<T>): Boolean

	/**
	 * [listener] is [IStorageEventConsumer] which want to unsubscribe from our events
	 */
	fun removeListener(listener: IStorageEventConsumer<T>): Boolean
}

/**
 * Consumes events produced by [IStorageEventConsumer]
 */
interface IStorageEventConsumer<T : IStorageStack> : IStorage<T> {
	/**
	 * Fired on whenever an object is added (to listener) we subscribed to
	 */
	fun addStack(stack: T, id: UUID, provider: IStorageProvider<T>)

	/**
	 * Fired on whenever an object is changes on listener we subscribed to
	 */
	fun changeStack(stack: T, id: UUID, oldCount: BigInteger)

	/**
	 * Fired on whenever an object is removed from listener we subscribed to
	 */
	fun removeStack(stack: T, id: UUID)
}

/**
 * Storage acceptor, accepts (insert only) stacks provided via [insertStack] method.
 */
interface IStorageAcceptor<T : IStorageStack> : IStorage<T> {
	/**
	 * Inserts an item into system.
	 * @return leftover, might equal to [stack] if no items were inserted
	 */
	fun insertStack(stack: T, simulate: Boolean): T
}

/**
 * Storage provider, provides (extract only) stacks to whoever needs them (the "warehouse").
 *
 * [get] methods work as in bidirectional map.
 *
 * It is **required** for storage having **exactly one or none** of mappings of one stack [T]
 * to one [UUID] (exactly one *UUID -> stack* and exactly one *stack -> UUID*).
 *
 * What this means is that [get] with [T] as an argument shall never experience a situation where
 * two stacks match provided key.
 *
 * Technical decision of using [UUID]s for primary key for locating stacks is performance.
 * The only downside is doubled memory usage for storing bidirectional map (implementation specific).
 * This allows us to avoid burden of constantly constructing keys out of stacks (e.g. [net.minecraft.world.item.ItemStack]s)
 * which may or may not produce performance hit; [UUID]s are lightweight, semantically not bound to anything and are
 * very good for distributed ID generation (so nothing in game has to be bound to one sequential number generator).
 */
interface IStorageProvider<T : IStorageStack> : IStorageEventProducer<T> {
	/**
	 * Attempts to retrieve stored stack by its [id].
	 *
	 * Returns stored stack as-is. If no stack with [id] exists, then
	 * "empty stack" is returned.
	 *
	 * @param id identifier of stack
	 * @return stored object (not a copy). Do not edit it.
	 */
	operator fun get(id: UUID): T

	/**
	 * Attempts to map given [key] to [UUID] inside this storage provider.
	 *
	 * Implementation requirement: There should **never** be a situation where one [key]
	 * matches two or more stacks. **Exactly one or none**.
	 *
	 * @param key stack we want to find UUID of
	 * @return UUID of stack if present
	 */
	operator fun get(key: T): UUID? {
		val key1 = key.let { if (it.count == BigInteger.ONE) it else it.key() }

		return stacks.filter {
			if (it.stack.count == BigInteger.ONE) {
				return@filter it.stack == key1
			} else {
				return@filter it.stack.key() == key1
			}
		}.findFirst().orElse(null)?.id
	}

	val stacks: Stream<IStorageTuple<T>>

	/**
	 * If tuple does not exist, returns empty stack
	 *
	 * @param id identifier of stack to extract
	 * @param amount amount of units to extract
	 * @param simulate whenever to simulate the action or not
	 * @return copy of object, with amount of units actually extracted
	 */
	fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T
}

fun <T : IStorageStack> IStorageProvider<T>.removeListenerAuto(listener: IStorageEventConsumer<T>): Boolean {
	if (removeListener(listener)) {
		for (stack in stacks) {
			listener.removeStack(stack.stack, stack.id)
		}

		return true
	}

	return false
}

fun <T : IStorageStack> IStorageProvider<T>.addListenerAuto(listener: IStorageEventConsumer<T>): Boolean {
	if (addListener(listener)) {
		for (stack in stacks) {
			listener.addStack(stack.stack, stack.id, this)
		}

		return true
	}

	return false
}

/**
 * Storage component, which basically implement Input and Output
 */
interface IStorageComponent<T : IStorageStack> : IStorageProvider<T>, IStorageAcceptor<T>

fun <T : IStorageStack> IStorageEventConsumer<T>.changeStack(tuple: IStorageTuple<T>, oldCount: BigInteger) {
	changeStack(tuple.stack, tuple.id, oldCount)
}

fun <T : IStorageStack> IStorageEventConsumer<T>.addStack(tuple: IStorageTuple<T>, provider: IStorageProvider<T>) {
	addStack(tuple.stack, tuple.id, provider)
}

fun <T : IStorageStack> IStorageEventConsumer<T>.removeStack(tuple: IStorageTuple<T>) {
	removeStack(tuple.stack, tuple.id)
}

interface IStorageTuple<T : IStorageStack> {
	val id: UUID
	val stack: T
}

class StorageTuple<T : IStorageStack>(override val id: UUID, override val stack: T) : IStorageTuple<T>

/**
 * Component which (most time) proxy other components (combine their contents into single view)
 */
interface IVirtualStorageComponent<T : IStorageStack> : IStorageComponent<T>, IStorageEventConsumer<T> {
	fun add(identity: IStorage<T>)
	fun remove(identity: IStorage<T>)
	fun contains(identity: IStorage<T>): Boolean
}
