
@file:Suppress("unused")

package ru.dbotthepony.mc.otm.core

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableMultimap
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import it.unimi.dsi.fastutil.objects.ObjectComparators
import net.minecraft.Util
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.HolderLookup
import net.minecraft.core.IdMap
import net.minecraft.core.Registry
import net.minecraft.core.SectionPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentContents
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.network.chat.contents.TranslatableContents
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.component.ItemAttributeModifiers
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeInput
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateHolder
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.chunk.ChunkSource
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.items.IItemHandler
import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.util.readVarIntLE
import ru.dbotthepony.mc.otm.core.util.writeVarIntLE
import java.io.InputStream
import java.io.OutputStream
import java.lang.ref.Reference
import java.math.BigInteger
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Future
import java.util.function.Consumer
import java.util.function.Supplier
import java.util.random.RandomGenerator
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.NoSuchElementException
import kotlin.jvm.optionals.getOrNull
import kotlin.math.ln
import kotlin.math.sqrt
import kotlin.reflect.KProperty

operator fun RecipeInput.get(index: Int): ItemStack = getItem(index)
operator fun CraftingInput.get(x: Int, y: Int): ItemStack = getItem(x, y)

operator fun CraftingInput.Positioned.get(x: Int, y: Int): ItemStack {
	val actualX = x - left
	val actualY = y - top

	if (actualX !in 0 until input.width() || actualY !in 0 until input.height())
		return ItemStack.EMPTY

	return input.getItem(actualX, actualY)
}

fun FriendlyByteBuf.writeBigInteger(value: BigInteger) {
	writeByteArray(value.toByteArray())
}

fun FriendlyByteBuf.readBigInteger(byteLimit: Int = 128) = BigInteger(readByteArray(byteLimit))

fun <V> Map<*, V>.asLambdaSupplierArray(): Array<() -> V> {
	val result = arrayOfNulls<() -> V>(size)
	var i = 0

	for (k in keys) {
		result[i++] = { this[k] ?: throw ConcurrentModificationException("Key $k is no longer present in map $this") }
	}

	return result as Array<() -> V>
}

fun <V> Map<*, V>.asSupplierArray(): Array<Supplier<V>> {
	val result = arrayOfNulls<Supplier<V>>(size)
	var i = 0

	for (k in keys) {
		result[i++] = { this[k] ?: throw ConcurrentModificationException("Key $k is no longer present in map $this") }
	}

	return result as Array<Supplier<V>>
}

fun <V> Future<V>.asSupplier(): Supplier<V> {
	return Supplier { get() }
}

fun <V> runInBackground(block: Callable<V>): Future<V> {
	return Util.backgroundExecutor().submit(block)
}

operator fun IItemHandler.get(index: Int): ItemStack = getStackInSlot(index)

operator fun JsonObject.set(s: String, value: JsonElement) = add(s, value)
operator fun JsonObject.set(s: String, value: String) = add(s, JsonPrimitive(value))
operator fun JsonObject.set(s: String, value: Number) = add(s, JsonPrimitive(value))
operator fun JsonObject.set(s: String, value: Boolean) = add(s, JsonPrimitive(value))

fun LevelAccessor.getBlockStateNow(pos: BlockPos): BlockState {
	return getBlockStateNowOrNull(pos) ?: Blocks.AIR.defaultBlockState()
}

fun LevelAccessor.getBlockStateNowOrNull(pos: BlockPos): BlockState? {
	return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos)
}

fun LevelAccessor.getBlockEntityNow(pos: BlockPos): BlockEntity? {
	return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockEntity(pos)
}

fun ChunkSource.getChunkNow(pos: BlockPos): LevelChunk? {
	return getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))
}

fun ChunkSource.getChunkNow(pos: ChunkPos): LevelChunk? {
	return getChunkNow(pos.x, pos.z)
}

inline val FluidStack.isNotEmpty get() = !isEmpty
inline val ItemStack.isNotEmpty get() = !isEmpty

inline var Entity.position: Vec3
	get() = position()
	set(value) { setPos(value) }

inline val <reified T : Enum<T>> T.next: T get() {
	val values = enumValues<T>()
	val next = (ordinal + 1) % values.size
	return values[next]
}

inline val <reified T : Enum<T>> T.prev: T get() {
	val values = enumValues<T>()
	var next = ordinal - 1

	if (next < 0) {
		next = values.size - 1
	}

	return values[next]
}

fun <T : Enum<T>> T.next(values: Array<out T>): T {
	val next = (ordinal + 1) % values.size
	return values[next]
}

fun <T : Enum<T>> T.prev(values: Array<out T>): T {
	var next = ordinal - 1

	if (next < 0) {
		next = values.size - 1
	}

	return values[next]
}

fun IntArray.shuffle(random: RandomSource): IntArray {
	for (i in lastIndex downTo 1) {
		val j = random.nextInt(i + 1)
		val copy = this[i]
		this[i] = this[j]
		this[j] = copy
	}

	return this
}

fun <T, L : MutableList<T>> L.shuffle(random: RandomSource): L {
	Util.shuffle(this, random)
	return this
}

fun <T> List<T>.random(random: RandomGenerator): T {
	if (isEmpty())
		throw NoSuchElementException("This list is empty")
	return get(random.nextInt(size))
}

fun <T> List<T>.random(random: RandomSource): T {
	if (isEmpty())
		throw NoSuchElementException("This list is empty")
	return get(random.nextInt(size))
}

inline fun <T : Any> immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList<T> {
	require(size >= 0) { "Invalid list size $size" }

	return when (size) {
		0 -> ImmutableList.of()
		1 -> ImmutableList.of(initializer(0))
		else -> ImmutableList.Builder<T>().let {
			for (i in 0 until size) {
				it.add(initializer(i))
			}

			it.build()
		}
	}
}

inline fun <K : Any, V : Any> immutableMap(initializer: ImmutableMap.Builder<K, V>.() -> Unit): ImmutableMap<K, V> {
	val builder = ImmutableMap.Builder<K, V>()
	initializer.invoke(builder)
	return builder.build()
}

inline fun <K : Any, V : Any> immutableMultimap(initializer: ImmutableMultimap.Builder<K, V>.() -> Unit): ImmutableMultimap<K, V> {
	val builder = ImmutableMultimap.Builder<K, V>()
	initializer.invoke(builder)
	return builder.build()
}

inline fun <V : Any> immutableSet(initializer: Consumer<V>.() -> Unit): ImmutableSet<V> {
	val builder = ImmutableSet.Builder<V>()
	initializer.invoke(builder::add)
	return builder.build()
}

inline fun <V : Any> immutableList(initializer: Consumer<V>.() -> Unit): ImmutableList<V> {
	val builder = ImmutableList.Builder<V>()
	initializer.invoke(builder::add)
	return builder.build()
}

fun <V : Any> immutableList(a: V, vararg values: V): ImmutableList<V> {
	val builder = ImmutableList.Builder<V>()
	builder.add(a)
	builder.addAll(values.iterator())
	return builder.build()
}

inline fun itemAttributes(builder: ItemAttributeModifiers.Builder.() -> Unit): ItemAttributeModifiers {
	val value = ItemAttributeModifiers.builder()
	builder(value)
	return value.build()
}

fun <T : Any> FriendlyByteBuf.writeType(registry: IdMap<T>, value: T) {
	writeInt(registry.getId(value))
}

fun FriendlyByteBuf.writeBlockType(value: Block) {
	writeType(BuiltInRegistries.BLOCK, value)
}

fun FriendlyByteBuf.writeBlockState(value: BlockState) {
	writeVarInt(Block.BLOCK_STATE_REGISTRY.getIdOrThrow(value))
}

fun FriendlyByteBuf.writeItemType(value: Item) {
	writeType(BuiltInRegistries.ITEM, value)
}

fun RegistryFriendlyByteBuf.writeItem(value: ItemStack) {
	ItemStack.OPTIONAL_STREAM_CODEC.encode(this, value)
}

fun RegistryFriendlyByteBuf.writeComponent(value: Component) {
	ComponentSerialization.STREAM_CODEC.encode(this, value)
}

fun OutputStream.writeItemType(value: Item) {
	writeVarIntLE(BuiltInRegistries.ITEM.getId(value))
}

fun <T : Any> FriendlyByteBuf.readType(registry: IdMap<T>): T {
	val id = readInt()
	return registry.byId(id) ?: throw NoSuchElementException("No such entry with ID $id")
}

fun FriendlyByteBuf.readBlockType(): Block {
	return readType(BuiltInRegistries.BLOCK)
}

fun FriendlyByteBuf.readBlockState(): BlockState {
	return Block.BLOCK_STATE_REGISTRY.byIdOrThrow(readVarInt())
}

fun FriendlyByteBuf.readItemType(): Item {
	return readType(BuiltInRegistries.ITEM)
}

fun RegistryFriendlyByteBuf.readItem(): ItemStack {
	return ItemStack.OPTIONAL_STREAM_CODEC.decode(this)
}

fun RegistryFriendlyByteBuf.readComponent(): Component {
	return ComponentSerialization.STREAM_CODEC.decode(this)
}

fun InputStream.readItemType(): Item {
	return BuiltInRegistries.ITEM.byId(readVarIntLE())
}

operator fun <T : Comparable<T>> StateHolder<*, *>.get(property: Property<T>): T {
	return getValue(property)
}

operator fun StateHolder<*, *>.get(property: BlockRotationFreedom): BlockRotation {
	return getValue(property.property)
}

operator fun <S : StateHolder<*, *>, T : Comparable<T>> S.set(property: Property<T>, value: T): S {
	return setValue(property, value) as S
}

fun <T> List<T>.toImmutableList(): List<T> {
	return ImmutableList.copyOf(this)
}

operator fun <R> (() -> R).getValue(thisRef: Any, property: KProperty<*>): R {
	return invoke()
}

fun BlockState.getExplosionResistance(level: BlockGetter, pos: BlockPos): Float {
	return try {
		getExplosionResistance(level, pos, null)
	} catch (err: NullPointerException) {
		block.explosionResistance
		// Потому что возможно какой-либо мод не ожидает что Explosion == null
		// особенно учитывая что интерфейс IForgeBlock не имеет @ParamsAreNonnullByDefault
		// и аргумент не помечен как @Nullable
		// тем самым имеет тип Explosion! который указывается как Explosion? .. Explosion!!
	} catch (err: IllegalArgumentException) {
		block.explosionResistance
	}
}

fun Level.gracefulBlockBreak(blockPos: BlockPos, block: BlockState = getBlockState(blockPos)) {
	val tile = getBlockEntity(blockPos)
	val state = block.fluidState.createLegacyBlock()
	block.onRemove(this, blockPos, state, false)
	Block.dropResources(block, this, blockPos, tile)
	block.block.destroy(this, blockPos, block)
	setBlock(blockPos, state, Block.UPDATE_ALL)
}

fun <E> MutableCollection<E>.addAll(elements: Iterator<E>) {
	for (item in elements) {
		add(item)
	}
}

fun <E> MutableCollection<E>.addAll(elements: Stream<out E>) {
	for (item in elements) {
		add(item)
	}
}

fun <E> Iterable<E>.stream(): Stream<out E> {
	return StreamSupport.stream(this.spliterator(), false)
}

fun <E> Iterator<E>.stream(): Stream<out E> {
	return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false)
}

fun Entity.genericPositions(): Collection<Vector> {
	return listOf(
		position,
		eyePosition,
		boundingBox.center
	)
}

fun ResourceLocation.toUUID(): UUID {
	return toString().toUUID()
}

fun String.toUUID(): UUID {
	return UUID.nameUUIDFromBytes(toByteArray(Charsets.UTF_8))
}

fun <T> Collection<T>.probablyParallelStream(): Stream<out T> {
	if (size >= 400) {
		// TODO: https://github.com/MinecraftForge/EventBus/issues/44
		return stream()
	}

	return stream()
}

fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)

@Suppress("unchecked_cast")
fun <T> Stream<T?>.filterNotNull(): Stream<T> {
	return filter { it != null } as Stream<T>
}

@Suppress("unchecked_cast")
inline fun <reified T> Stream<*>.filterIsInstance(): Stream<T> {
	return filter { it is T } as Stream<T>
}

inline fun <T> MutableList<out Reference<out T>>.forValidRefs(fn: (T) -> Unit) {
	val iterator = listIterator()

	for (value in iterator) {
		val get = value.get()

		if (get == null) {
			iterator.remove()
		} else {
			fn.invoke(get)
		}
	}
}

inline fun <T> MutableList<out Reference<out T>>.forValidRefsBreak(fn: (T) -> Boolean) {
	val iterator = listIterator()

	for (value in iterator) {
		val get = value.get()

		if (get == null) {
			iterator.remove()
		} else {
			if (fn.invoke(get)) {
				break
			}
		}
	}
}

val ComponentContents.key: String
	get() = (this as? TranslatableContents ?: throw ClassCastException("$this is not a TranslatableContents")).key

fun <T : Comparable<T>> BlockState.getValueNullable(prop: Property<T>): T? {
	if (hasProperty(prop)) {
		return getValue(prop)
	}

	return null
}

fun <T> Stream<T>.asIterable(): Iterable<T> {
	return object : Iterable<T> {
		override fun iterator(): Iterator<T> {
			return this@asIterable.iterator()
		}
	}
}

fun <T> Comparator<T>.nullsFirst(): Comparator<T> {
	return Comparator.nullsFirst(this)
}

fun <T> Comparator<T>.nullsLast(): Comparator<T> {
	return Comparator.nullsLast(this)
}

class MappedComparator<T, O>(private val parent: Comparator<O>, private val mapper: (T) -> O) : Comparator<T> {
	override fun compare(o1: T, o2: T): Int {
		return parent.compare(mapper.invoke(o1), mapper.invoke(o2))
	}

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

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

	override fun toString(): String {
		return "MappedComparator[$parent]"
	}
}

fun <A, B> Comparator<A>.map(mapper: (B) -> A): Comparator<B> {
	return MappedComparator(this, mapper)
}

fun <T> Comparator<T>.suppliers(): Comparator<Supplier<T>> {
	return MappedComparator(this) { it.get() }
}

/**
 * Returns applicable index to put [element] into [List] determined by [comparator], optionally specifying ranges as [fromIndex] and [toIndex]
 *
 * If [List] is not sorted, result of this function is undefined
 */
fun <E> List<E>.searchInsertionIndex(element: E, comparator: Comparator<in E>, fromIndex: Int = 0, toIndex: Int = size): Int {
	require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" }
	require(fromIndex >= 0) { "Invalid from index: $fromIndex" }
	require(toIndex >= 0) { "Invalid to index: $toIndex" }
	require(fromIndex <= size) { "Invalid from index: $fromIndex (list size $size)" }
	require(toIndex <= size) { "Invalid to index: $toIndex (list size $size)" }

	if (fromIndex == size || fromIndex == toIndex || comparator.compare(element, this[fromIndex]) <= 0) {
		return fromIndex
	}

	// линейный поиск если границы маленькие
	if (toIndex - fromIndex <= 10) {
		for (i in fromIndex + 1 until toIndex) {
			val compare = comparator.compare(element, this[i])

			if (compare <= 0) {
				return i
			}
		}

		return size
	} else {
		// двоичный поиск
		var lower = fromIndex
		var upper = toIndex - 1

		while (upper - lower >= 10) {
			val middle = (upper + lower) / 2
			val compare = comparator.compare(element, this[middle])

			if (compare == 0) {
				return middle
			} else if (compare < 0) {
				upper = middle
			} else {
				lower = middle
			}
		}

		return searchInsertionIndex(element, comparator, lower, upper + 1)
	}
}

/**
 * Inserts [element] into [MutableList] at index determined by [comparator]
 *
 * If [MutableList] is not sorted, result of this function is undefined
 */
fun <E> MutableList<E>.addSorted(element: E, comparator: Comparator<in E>) {
	add(searchInsertionIndex(element, comparator), element)
}

/**
 * Inserts [element] into [MutableList] at index determined by comparing values themselves
 *
 * If [MutableList] is not sorted, result of this function is undefined
 */
fun <E : Comparable<E>> MutableList<E>.addSorted(element: E) {
	add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element)
}

fun <A, B> lazy2(a: () -> A, b: A.() -> B): Supplier<B> {
	val first = lazy(a)
	return Supplier { b.invoke(first.value) }
}

fun <T : Any> HolderLookup.Provider.lookupOrThrow(key: ResourceKey<T>): Holder<T> {
	return lookupOrThrow(key.registryKey()).getOrThrow(key)
}

fun <T : Any> Registry<T>.getHolder(value: T): Holder<T>? {
	// this is so stupid
	return getHolder(getResourceKey(value).getOrNull() ?: return null).getOrNull()
}

fun <T : Any> Registry<T>.getHolderOrThrow(value: T): Holder<T> {
	// this is so stupid
	return getHolder(getResourceKey(value).orElseThrow()).orElseThrow()
}

// forge registry functionality emulation on vanilla registry
fun <T : Any> Registry<T>.getReverseTag(value: T): Stream<TagKey<T>> {
	return getHolder(value)?.tags() ?: Stream.empty()
}

// forge > neoforge moment
infix fun FluidStack.isSameFluidSameComponents(other: FluidStack): Boolean {
	return FluidStack.isSameFluidSameComponents(this, other)
}

infix fun FluidStack.isSameFluid(other: FluidStack): Boolean {
	return FluidStack.isSameFluid(this, other)
}

infix fun FluidStack.isNotSameFluidSameComponents(other: FluidStack): Boolean {
	return !FluidStack.isSameFluidSameComponents(this, other)
}

infix fun FluidStack.isNotSameFluid(other: FluidStack): Boolean {
	return !FluidStack.isSameFluid(this, other)
}

infix fun FluidStack.isSameAs(other: FluidStack): Boolean {
	return FluidStack.isSameFluidSameComponents(this, other) && amount == other.amount
}

infix fun FluidStack.isNotSameAs(other: FluidStack): Boolean {
	return !FluidStack.isSameFluidSameComponents(this, other) && amount == other.amount
}

data class DoublePair(val first: Double, val second: Double)

fun RandomSource.nextUUID(): UUID {
	return UUID(nextLong(), nextLong())
}

// normal distribution via Marsaglia polar method
fun RandomGenerator.nextNormalDoubles(stddev: Double, mean: Double): DoublePair {
	var rand1: Double
	var rand2: Double
	var distSqr: Double

	do {
		rand1 = 2.0 * nextDouble() - 1.0
		rand2 = 2.0 * nextDouble() - 1.0
		distSqr = rand1 * rand1 + rand2 * rand2
	} while (distSqr >= 1.0 || distSqr == 0.0)

	val mapping = sqrt(-2.0 * ln(distSqr) / distSqr)

	return DoublePair(
		rand1 * mapping * stddev + mean,
		rand2 * mapping * stddev + mean
	)
}

// All Mojang's random sources use MarsagliaPolarGaussian for generating normal distributed doubles
fun RandomSource.nextNormalDoubles(stddev: Double, mean: Double): DoublePair {
	return DoublePair(
		nextGaussian() * stddev + mean,
		nextGaussian() * stddev + mean
	)
}

fun RandomSource.nextNormalDouble(stddev: Double, mean: Double): Double {
	return nextGaussian() * stddev + mean
}
