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

import it.unimi.dsi.fastutil.ints.Int2IntFunction
import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntIterators
import it.unimi.dsi.fastutil.objects.ObjectIterators
import ru.dbotthepony.mc.otm.core.addAll
import java.util.Optional
import java.util.Spliterator
import java.util.Spliterators
import java.util.function.BinaryOperator
import java.util.function.IntPredicate
import java.util.function.Predicate
import java.util.function.Supplier
import java.util.stream.Collector
import java.util.stream.Stream
import java.util.stream.StreamSupport

// Purpose of Stream API over Iterators is that it is simple enough for JIT to inline most of it,
// unlike actual Streams.

// We lose only one (actual) element of Stream API here: ability to parallelize work,
// except it doesn't (properly) work in Minecraft (Forge) either, due to classloader bug:
// https://github.com/MinecraftForge/EventBus/issues/44

// Aside parallel work, unimplemented Stream API elements can be easily implemented when required

private class FilteringIterator<T>(private val parent: Iterator<T>, private val predicate: Predicate<in T>, private var value: T) : MutableIterator<T> {
	private var hasValue = true
	private var returned = false

	override fun hasNext(): Boolean {
		return hasValue
	}

	override fun next(): T {
		if (!hasValue) throw NoSuchElementException()
		hasValue = false
		returned = true

		val value = this.value

		while (parent.hasNext()) {
			val next = parent.next()

			if (predicate.test(next)) {
				hasValue = true
				this.value = next
				break
			}
		}

		return value
	}

	override fun remove() {
		if (!returned) throw NoSuchElementException()
		returned = false
		(parent as MutableIterator<T>).remove()
	}
}

private class MappingIterator<T, R>(private val parent: Iterator<T>, private val mapper: (T) -> R) : MutableIterator<R> {
	override fun hasNext(): Boolean {
		return parent.hasNext()
	}

	override fun next(): R {
		return mapper.invoke(parent.next())
	}

	override fun remove() {
		(parent as MutableIterator<T>).remove()
	}
}

private class FlatMappingIterator<T, R>(private val parent: Iterator<T>, private val mapper: (T) -> Iterator<R>) : MutableIterator<R> {
	private var current: Iterator<R> = mapper.invoke(parent.next())
	private var last: Iterator<R>? = null

	init {
		while (!current.hasNext() && parent.hasNext()) {
			current = mapper.invoke(parent.next())
		}
	}

	override fun hasNext(): Boolean {
		return current.hasNext()
	}

	override fun next(): R {
		if (!current.hasNext())
			throw NoSuchElementException()

		val v = current.next()
		last = current

		while (!current.hasNext() && parent.hasNext()) {
			current = mapper.invoke(parent.next())
		}

		return v
	}

	override fun remove() {
		(last as MutableIterator<R>? ?: throw NoSuchElementException()).remove()
		last = null
	}
}

private class LimitingIterator<T>(private val parent: Iterator<T>, private val limit: Long) : Iterator<T> {
	init {
		require(limit > 0) { "Invalid limit $limit" }
	}

	private var found = 0L

	override fun hasNext(): Boolean {
		return found < limit && parent.hasNext()
	}

	override fun next(): T {
		if (found >= limit)
			throw NoSuchElementException()

		return parent.next()
	}
}

private class SkippingIterator<T>(private val parent: Iterator<T>, skip: Long) : MutableIterator<T> {
	init {
		require(skip >= 0) { "Invalid skip amount $skip" }
	}

	private var found = skip
	private var returned = false

	override fun hasNext(): Boolean {
		while (parent.hasNext() && found > 0L) {
			found--
			parent.next()
		}

		return parent.hasNext()
	}

	override fun next(): T {
		if (!hasNext())
			throw NoSuchElementException()

		val v = parent.next()
		returned = true
		return v
	}

	override fun remove() {
		if (!returned) {
			throw NoSuchElementException()
		}

		returned = false
		(parent as MutableIterator<T>).remove()
	}
}

fun <T> concatIterators(): MutableIterator<T> {
	return ObjectIterators.EMPTY_ITERATOR as MutableIterator<T>
}

fun <T> concatIterators(a: Iterator<T>): MutableIterator<T> {
	return a as MutableIterator<T>
}

fun <T> concatIterators(iterators: Iterable<Iterator<T>>): MutableIterator<T> {
	return iterators.iterator().flatMap { it }
}

fun <T> concatIterators(vararg iterators: Iterator<T>): MutableIterator<T> {
	return iterators.iterator().flatMap { it }
}

/**
 * Filters elements of [this] iterator
 *
 * Resulting [Iterator] is [MutableIterator] if [this] is
 */
fun <T> Iterator<T>.filter(condition: Predicate<in T>): MutableIterator<T> {
	while (hasNext()) {
		val v = next()

		if (condition.test(v)) {
			return FilteringIterator(this, condition, v)
		}
	}

	return emptyIterator()
}

/**
 * Maps elements of [this] iterator from values of [T] to [R] using function [mapper]
 *
 * Resulting [Iterator] is [MutableIterator] if [this] is
 */
fun <T, R> Iterator<T>.map(mapper: (T) -> R): MutableIterator<R> {
	if (!hasNext()) {
		return emptyIterator()
	}

	return MappingIterator(this, mapper)
}

/**
 * Maps elements of [this] iterator from type [T] to other iterators of type [R] using function [mapper]
 *
 * Resulting [Iterator] is [MutableIterator] if [this] is
 */
fun <T, R> Iterator<T>.flatMap(mapper: (T) -> Iterator<R>): MutableIterator<R> {
	if (!hasNext()) {
		return emptyIterator()
	}

	return FlatMappingIterator(this, mapper)
}

inline fun <T> Iterator<T>.reduce(identity: T, reducer: (T, T) -> T): T {
	var result = identity
	while (hasNext()) result = reducer.invoke(result, next())
	return result
}

fun <T> Iterator<T?>.filterNotNull(): MutableIterator<T> = filter { it != null } as MutableIterator<T>

inline fun <reified T> Iterator<*>.filterIsInstance(): MutableIterator<T> = filter { it is T } as MutableIterator<T>

fun <T> Iterator<T>.any(predicate: Predicate<in T>): Boolean {
	while (hasNext())
		if (predicate.test(next()))
			return true

	return false
}

inline fun <T> Iterator<T>.any(predicate: (T) -> Boolean): Boolean {
	while (hasNext())
		if (predicate.invoke(next()))
			return true

	return false
}

fun <T> Iterator<T>.all(predicate: Predicate<in T>): Boolean {
	while (hasNext())
		if (!predicate.test(next()))
			return false

	return true
}

inline fun <T> Iterator<T>.all(predicate: (T) -> Boolean): Boolean {
	while (hasNext())
		if (!predicate.invoke(next()))
			return false

	return true
}

fun <T> Iterator<T>.none(predicate: Predicate<in T>): Boolean {
	while (hasNext())
		if (predicate.test(next()))
			return false

	return true
}

inline fun <T> Iterator<T>.none(predicate: (T) -> Boolean): Boolean {
	while (hasNext())
		if (predicate.invoke(next()))
			return false

	return true
}

fun <T, A, R> Iterator<T>.collect(collector: Collector<T, A, R>): R {
	val accumulator = collector.accumulator()
	val instance = collector.supplier().get()

	for (value in this) {
		accumulator.accept(instance, value)
	}

	return collector.finisher().apply(instance)
}

fun <T> Iterator<T>.toList(expectedSize: Int = 16): MutableList<T> {
	val result = ArrayList<T>(expectedSize)
	result.addAll(this)
	return result
}

fun <T> Iterator<T>.toImmutableList(expectedSize: Int = 16): List<T> {
	if (!hasNext())
		return emptyList()

	return toList(expectedSize)
}

fun <T : Any> Iterator<T>.find(): Optional<T> {
	if (hasNext()) {
		return Optional.of(next())
	}

	return Optional.empty<T>()
}

fun <T> Iterator<T>.limit(limit: Long): Iterator<T> = LimitingIterator(this, limit)
fun <T> Iterator<T>.skip(skip: Long): Iterator<T> = if (skip == 0L) this else SkippingIterator(this, skip)

inline fun <T> Iterator<T>.forEach(action: (T) -> Unit) {
	for (value in this) {
		action.invoke(value)
	}
}

fun <T : Any> Iterator<T>.min(comparator: Comparator<T>): Optional<T> {
	if (!hasNext()) {
		return Optional.empty<T>()
	}

	var min = next()

	for (value in this) {
		if (comparator.compare(min, value) > 0) {
			min = value
		}
	}

	return Optional.of(min)
}

fun <T : Any> Iterator<T>.max(comparator: Comparator<T>): Optional<T> {
	if (!hasNext()) {
		return Optional.empty<T>()
	}

	var max = next()

	for (value in this) {
		if (comparator.compare(max, value) < 0) {
			max = value
		}
	}

	return Optional.of(max)
}

fun <T> Iterator<T>.peek(peeker: (T) -> Unit): MutableIterator<T> {
	return object : MutableIterator<T> {
		override fun hasNext(): Boolean {
			return this@peek.hasNext()
		}

		override fun next(): T {
			return this@peek.next().also(peeker)
		}

		override fun remove() {
			(this@peek as MutableIterator<T>).remove()
		}
	}
}

fun <T> Iterator<T>.maybe(): T? {
	return if (hasNext())
		next()
	else
		null
}

fun <T> emptyIterator(): MutableIterator<T> {
	return ObjectIterators.emptyIterator()
}

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

fun <T> Iterator<T>.allEqual(): Boolean {
	if (hasNext()) {
		val v = next()

		while (hasNext()) {
			if (v != next()) {
				return false
			}
		}

		return true
	}

	return false
}

fun <T> Iterator<T>.count(): Long {
	var count = 0L
	while (hasNext()) count++
	return count
}
