package ru.dbotthepony.mc.otm.data

import com.google.common.collect.ImmutableList
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import ru.dbotthepony.mc.otm.core.stream
import java.util.function.Predicate
import java.util.stream.Stream

class CodecList<S : Any>(codecs: Stream<Codec<S>>) : Codec<S> {
	constructor(codecs: Collection<Codec<S>>) : this(codecs.stream())
	constructor(vararg codecs: Codec<S>) : this(codecs.stream() as Stream<Codec<S>>)

	private val codecs = codecs.collect(ImmutableList.toImmutableList())

	init {
		require(this.codecs.isNotEmpty()) { "No codecs provided" }
	}

	override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
		val results = ArrayList<DataResult<T>>(codecs.size)

		for (codec in codecs) {
			val result = codec.encode(input, ops, prefix)

			if (result.result().isPresent) {
				return result
			} else {
				results.add(result)
			}
		}

		return DataResult.error {
			"None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() }
		}
	}

	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<S, T>> {
		val results = ArrayList<DataResult<Pair<S, T>>>(codecs.size)

		for (codec in codecs) {
			val result = codec.decode(ops, input)

			if (result.result().isPresent) {
				return result
			} else {
				results.add(result)
			}
		}

		return DataResult.error {
			"None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() }
		}
	}
}

class PredicatedCodecList<S : Any>(codecs: Stream<kotlin.Pair<Codec<S>, Predicate<S>>>) : Codec<S> {
	constructor(codecs: Collection<kotlin.Pair<Codec<S>, Predicate<S>>>) : this(codecs.stream())
	constructor(vararg codecs: kotlin.Pair<Codec<S>, Predicate<S>>) : this(codecs.stream() as Stream<kotlin.Pair<Codec<S>, Predicate<S>>>)

	private val codecs = codecs.collect(ImmutableList.toImmutableList())

	init {
		require(this.codecs.isNotEmpty()) { "No codecs provided" }
	}

	override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
		val results = ArrayList<DataResult<T>>(codecs.size)
		var i = -1

		for ((codec, predicate) in codecs) {
			i++

			if (predicate.test(input)) {
				val result = codec.encode(input, ops, prefix)

				if (result.result().isPresent) {
					return result
				} else {
					results.add(result)
				}
			} else {
				val i2 = i
				results.add(DataResult.error { "Codec #$i2 predicate tested false" })
			}
		}

		return DataResult.error {
			"None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() }
		}
	}

	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<S, T>> {
		val results = ArrayList<DataResult<Pair<S, T>>>(codecs.size)

		for ((codec) in codecs) {
			val result = codec.decode(ops, input)

			if (result.result().isPresent) {
				return result
			} else {
				results.add(result)
			}
		}

		return DataResult.error {
			"None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() }
		}
	}
}
