package ru.dbotthepony.mc.otm.storage

import it.unimi.dsi.fastutil.Hash
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.item.ItemStack
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.getValue
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.readBigInteger
import ru.dbotthepony.mc.otm.core.readItem
import ru.dbotthepony.mc.otm.core.writeBigInteger
import ru.dbotthepony.mc.otm.core.writeItem
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.RegistryDelegate
import java.math.BigInteger

abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
	init {
		require(count >= BigInteger.ZERO) { "Negative amount of things in stack: $count" }
	}

	abstract val type: Type<S>
	abstract fun copy(newCount: BigInteger): S

	abstract fun equalsWithoutCount(other: StorageStack<*>): Boolean
	abstract fun hashCodeWithoutCount(): Int

	open val isEmpty: Boolean get() = count == BigInteger.ZERO
	inline val isNotEmpty get() = !isEmpty

	open val maxStackSize: BigInteger? get() = null

	abstract override fun equals(other: Any?): Boolean
	abstract override fun hashCode(): Int

	fun grow(amount: BigInteger): S {
		if (amount == BigInteger.ZERO) return this as S
		val newCount = count + amount

		if (newCount <= BigInteger.ZERO)
			return type.empty

		return copy(newCount)
	}

	fun shrink(amount: BigInteger): S {
		if (amount == BigInteger.ZERO) return this as S
		val newCount = count - amount

		if (newCount <= BigInteger.ZERO)
			return type.empty

		return copy(newCount)
	}

	fun write(buff: RegistryFriendlyByteBuf) {
		type.write(buff, this as S)
	}

	interface Type<T : StorageStack<*>> {
		val empty: T

		fun read(buff: RegistryFriendlyByteBuf): T
		fun write(buff: RegistryFriendlyByteBuf, value: T)

		/**
		 * If there is not enough energy for operation, it is completely cancelled
		 */
		fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack)

		/**
		 * If there is not enough energy for operation, it is completely cancelled
		 */
		fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack)

		/**
		 * If there is not enough energy for operation, it is completely cancelled
		 */
		fun energyPerOperation(stack: T): Decimal
	}

	class SimpleType<T : StorageStack<*>>(
		val energyPerOperation: Decimal,
		override val empty: T,
		private val read: (RegistryFriendlyByteBuf) -> T,
		private val write: (RegistryFriendlyByteBuf, T) -> Unit
	) : Type<T> {
		override fun read(buff: RegistryFriendlyByteBuf): T {
			return read.invoke(buff)
		}

		override fun write(buff: RegistryFriendlyByteBuf, value: T) {
			write.invoke(buff, value)
		}

		override fun energyPerOperation(stack: T): Decimal {
			return energyPerOperation * stack.count
		}
	}

	companion object : Hash.Strategy<StorageStack<*>?> {
		override fun equals(a: StorageStack<*>?, b: StorageStack<*>?): Boolean {
			if (a == null && b == null) return true
			if (a != null && b != null) return a.equalsWithoutCount(b)
			return false
		}

		override fun hashCode(o: StorageStack<*>?): Int {
			return o?.hashCodeWithoutCount() ?: 0
		}

		private val delegate = RegistryDelegate<Type<*>>("stack_type") {}
		val REGISTRY by delegate
		val REGISTRY_KEY by delegate::key

		private val registrar = MDeferredRegister(REGISTRY_KEY, OverdriveThatMatters.MOD_ID)

		val ITEMS: Type<ItemStorageStack> by registrar.register("items") {
			SimpleType(
				Decimal("4"),
				ItemStorageStack(ItemStack.EMPTY),
				{
					ItemStorageStack.unsafe(it.readItem(), it.readBigInteger())
				},
				{ buff, v ->
					buff.writeItem(v.toItemStack(1))
					buff.writeBigInteger(v.count)
				}
			)
		}

		fun register(bus: IEventBus) {
			bus.addListener(delegate::build)
			registrar.register(bus)
		}
	}
}
