package ru.dbotthepony.mc.otm.data.codec

import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import it.unimi.dsi.fastutil.bytes.ByteArrayList
import net.minecraft.nbt.NbtOps
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.Decimal.Companion.fromByteArray
import ru.dbotthepony.mc.otm.network.wrap
import java.nio.ByteBuffer

object DecimalCodec : Codec<Decimal> {
	val NETWORK = object : StreamCodec<FriendlyByteBuf, Decimal> {
		override fun decode(buf: FriendlyByteBuf): Decimal {
			return fromByteArray(buf.readByteArray())
		}

		override fun encode(buf: FriendlyByteBuf, p_320396_: Decimal) {
			buf.writeByteArray(p_320396_.toByteArray())
		}
	}.wrap()

	override fun <T : Any> encode(input: Decimal, ops: DynamicOps<T>, prefix: T): DataResult<T> {
		if (ops === NbtOps.INSTANCE) {
			// this is a hack, but at least we will get binary representation in binary file
			return DataResult.success((ops as DynamicOps<T>).createByteList(ByteBuffer.wrap(input.toByteArray())))
		}

		return DataResult.success(ops.createString(input.toString()))
	}

	override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Decimal, T>> {
		return ops.getStringValue(input).flatMap {
			try {
				DataResult.success(Pair(Decimal(it), ops.empty()))
			} catch (err: NumberFormatException) {
				DataResult.error { "Not a valid number for converting into Decimal: $it" }
			}
		}.mapOrElse(
			{
				DataResult.success(it)
			},
			{ e0 ->
				ops.getIntStream(input).flatMap {
					try {
						DataResult.success(Pair(fromByteArray(
							it
								.collect(::ByteArrayList, { v, a -> v.add(a.toByte()) }, ByteArrayList::addAll)
								.toByteArray()
						), ops.empty()))
					} catch (err: NumberFormatException) {
						DataResult.error { "Failed to convert array of bytes into Decimal: $it" }
					}
				}.mapOrElse(
					{
						DataResult.success(it)
					},
					{ e1 ->
						ops.getNumberValue(input).flatMap {
							DataResult.success(Pair(Decimal(it.toString()), ops.empty()))
						}.mapOrElse(
							{
								DataResult.success(it)
							},
							{ e2 ->
								DataResult.error { "None of attempts at decoding Decimal were successful: ${e0.message()}; ${e1.message()}; ${e2.message()}" }
							}
						)
					}
				)
			}
		)
	}
}
