package ru.dbotthepony.mc.otm.core.math

import net.minecraft.nbt.ByteArrayTag
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.util.RandomSource
import net.neoforged.neoforge.common.ModConfigSpec
import ru.dbotthepony.mc.otm.config.ObservedConfigValue
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.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
import java.math.RoundingMode

private val BI_MINUS_ONE = -BigInteger.ONE
private val PERCENTAGE_CONTEXT = MathContext(6) // 6 ибо это число знаков для вычисления процента

fun Decimal(value: Byte) = Decimal.valueOf(value)
fun Decimal(value: Short) = Decimal.valueOf(value)
fun Decimal(value: Int) = Decimal.valueOf(value)
fun Decimal(value: Long) = Decimal.valueOf(value)
fun Decimal(value: Float) = Decimal.valueOf(value)
fun Decimal(value: Double) = Decimal.valueOf(value)
fun Decimal(value: BigDecimal) = Decimal.valueOf(value)
fun Decimal(value: BigInteger) = Decimal.valueOf(value)
fun Decimal(value: String) = Decimal.valueOf(value)

/**
 * Fixed point arbitrary precision Decimal value. Values of this class embed [BigInteger] unscaled value, scale
 * is defined at compile time by [PRECISION].
 */
sealed class Decimal : Number(), Comparable<Decimal> {
	/**
	 * Whole part of this Decimal
	 */
	abstract val whole: BigInteger

	/**
	 * Arbitrary fractional part of this Decimal, as [BigInteger]
	 *
	 * Makes sense only when utilized along [PRECISION], [PRECISION_POW], [PRECISION_POW_BI]
	 */
	abstract val fractional: BigInteger

	/**
	 * *Signed* normalized (-1,1) fractional part of this Decimal, as [Float]
	 */
	inline val fractionalFloat: Float get() {
		return fractional.toFloat() / PRECISION_FLOAT
	}

	/**
	 * *Signed* normalized (-1,1) fractional part of this Decimal, as [Double]
	 */
	inline val fractionalDouble: Double get() {
		return fractional.toDouble() / PRECISION_DOUBLE
	}

	abstract operator fun plus(other: Decimal): Decimal
	abstract operator fun minus(other: Decimal): Decimal
	abstract operator fun times(other: Decimal): Decimal
	abstract operator fun div(other: Decimal): Decimal

	abstract operator fun rem(other: Decimal): Decimal

	// Primitive operators
	abstract operator fun plus(other: Float): Decimal
	abstract operator fun minus(other: Float): Decimal
	abstract operator fun times(other: Float): Decimal
	abstract operator fun div(other: Float): Decimal

	abstract operator fun plus(other: Double): Decimal
	abstract operator fun minus(other: Double): Decimal
	abstract operator fun times(other: Double): Decimal
	abstract operator fun div(other: Double): Decimal

	abstract operator fun plus(other: Int): Decimal
	abstract operator fun minus(other: Int): Decimal
	abstract operator fun times(other: Int): Decimal
	abstract operator fun div(other: Int): Decimal

	abstract operator fun plus(other: Long): Decimal
	abstract operator fun minus(other: Long): Decimal
	abstract operator fun times(other: Long): Decimal
	abstract operator fun div(other: Long): Decimal

	abstract operator fun plus(other: BigInteger): Decimal
	abstract operator fun minus(other: BigInteger): Decimal
	abstract operator fun times(other: BigInteger): Decimal
	abstract operator fun div(other: BigInteger): Decimal
	// /Primitive operators

	// "de-virtualize" generic method
	abstract override fun compareTo(other: Decimal): Int

	abstract operator fun unaryMinus(): Decimal
	operator fun unaryPlus() = this

	/**
	 * Sign number of this Decimal, as defined by [BigInteger.signum]
	 */
	abstract fun signum(): Int

	/**
	 * Whenever this Decimal is negative (less than zero)
	 */
	inline val isNegative get() = signum() < 0

	/**
	 * Whenever this Decimal is positive (bigger than zero)
	 */
	inline val isPositive get() = signum() > 0

	/**
	 * Whenever this Decimal is zero
	 */
	inline val isZero get() = signum() == 0
	inline val isNotZero get() = signum() != 0

	abstract val isInfinite: Boolean
	abstract val isFinite: Boolean

	/**
	 * Alias for [coerceAtLeast] with [ZERO] constant, except this method might
	 * perform faster.
	 */
	fun moreThanZero(): Decimal {
		if (signum() >= 0)
			return this

		return Zero
	}

	/**
	 * Alias for [coerceAtMost] with [ZERO] constant, except this method might
	 * perform faster.
	 */
	fun lessOrZero(): Decimal {
		if (signum() <= 0)
			return this

		return Zero
	}

	abstract fun toByteArray(): ByteArray

	fun serializeNBT(): Tag {
		return ByteArrayTag(toByteArray())
	}

	fun write(buff: FriendlyByteBuf) {
		buff.writeByteArray(toByteArray())
	}

	val absoluteValue: Decimal
		get() {
			return if (isNegative) {
				-this
			} else {
				this
			}
		}

	/**
	 * Truncates fractional part of this Decimal
	 */
	abstract fun floor(): Decimal

	abstract fun toString(decimals: Int): String
	override fun toString(): String = toString(-1)

	abstract fun toBigDecmial(): BigDecimal

	fun percentage(divisor: Decimal): Float {
		if (isZero || divisor.isZero) return 0f

		if (this >= divisor)
			return 1f
		else if (this <= FLOAT_MAX_VALUE && divisor <= FLOAT_MAX_VALUE)
			return (toDouble() / divisor.toDouble()).toFloat()
		else if (divisor === PositiveInfinity || divisor === NegativeInfinity)
			return 0f

		return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat()
	}

	private class Regular(val mag: BigInteger, marker: Nothing?) : Decimal() {
		constructor(value: BigInteger) : this(value * PRECISION_POW_BI, null)
		constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null)
		constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble()))
		constructor(value: Double) : this(BigDecimal(value))
		constructor(value: String) : this(BigDecimal(value))

		override val isInfinite: Boolean
			get() = false
		override val isFinite: Boolean
			get() = true

		private var _whole: BigInteger? = null
		private var _fractional: BigInteger? = null

		private fun computeWholeFractional(): BigInteger {
			val (a, b) = mag.divideAndRemainder(PRECISION_POW_BI)
			_whole = a
			_fractional = b
			return a
		}

		override val whole: BigInteger get() {
			return _whole ?: computeWholeFractional()
		}

		override val fractional: BigInteger get() {
			if (_fractional == null) computeWholeFractional()
			return _fractional!!
		}

		override fun toDouble(): Double {
			return mag.toDouble() / PRECISION_DOUBLE
		}

		override fun toFloat(): Float {
			return mag.toFloat() / PRECISION_FLOAT
		}

		override fun toByte(): Byte {
			return whole.toByte()
		}

		override fun toShort(): Short {
			return whole.toShort()
		}

		override fun signum(): Int {
			return mag.signum()
		}

		private var intCache = 0
		private var intComputed = false

		override fun toInt(): Int {
			if (!intComputed) {
				intCache = if (whole > BI_INT_MAX) {
					Int.MAX_VALUE
				} else if (whole < BI_INT_MIN) {
					Int.MIN_VALUE
				} else {
					whole.toInt()
				}

				intComputed = true
			}

			return intCache
		}

		private var longCache = 0L
		private var longComputed = false

		override fun toLong(): Long {
			if (!longComputed) {
				longCache = if (whole > BI_LONG_MAX) {
					Long.MAX_VALUE
				} else if (whole < BI_LONG_MIN) {
					Long.MIN_VALUE
				} else {
					whole.toLong()
				}
			}

			return longCache
		}

		override fun compareTo(other: Decimal): Int  {
			return if (other === Zero)
				signum()
			else if (other is Regular)
				mag.compareTo(other.mag)
			else if (other === PositiveInfinity)
				-1
			else if (other === NegativeInfinity)
				1
			else
				throw RuntimeException("unreachable code")
		}


		override fun plus(other: Decimal): Decimal {
			return if (other is Regular) {
				raw(mag + other.mag)
			} else if (other === Zero) {
				this
			} else if (other === PositiveInfinity) {
				PositiveInfinity
			} else if (other === NegativeInfinity) {
				NegativeInfinity
			} else {
				throw RuntimeException("Unreachable code")
			}
		}

		override fun minus(other: Decimal): Decimal {
			return if (other is Regular) {
				raw(mag - other.mag)
			} else if (other === Zero) {
				this
			} else if (other === PositiveInfinity) {
				NegativeInfinity
			} else if (other === NegativeInfinity) {
				PositiveInfinity
			} else {
				throw RuntimeException("Unreachable code")
			}
		}

		override fun times(other: Decimal): Decimal {
			if (other is Regular) {
				if (other == ONE) {
					return this
				} else if (other == MINUS_ONE) {
					return raw(-mag)
				}

				val result = mag * other.mag
				val (a, b) = result.divideAndRemainder(PRECISION_POW_BI)

				return if (b >= PRECISION_POW_BI_HIGH) {
					raw(a + BigInteger.ONE)
				} else {
					raw(a)
				}
			} else if (other === Zero) {
				return Zero
			} else if (other === PositiveInfinity) {
				return PositiveInfinity
			} else if (other === NegativeInfinity) {
				return NegativeInfinity
			} else {
				throw RuntimeException("Unreachable code")
			}
		}

		override fun div(other: Decimal): Decimal {
			if (other is Regular) {
				if (other == ONE) {
					return this
				} else if (other == MINUS_ONE) {
					return raw(-mag)
				}

				return raw((mag * PRECISION_POW_BI) / other.mag)
			} else if (other === Zero) {
				throw ArithmeticException("$this / 0")
			} else if (other === PositiveInfinity || other === NegativeInfinity) {
				return Zero
			} else {
				throw RuntimeException("Unreachable code")
			}
		}

		override fun rem(other: Decimal): Decimal {
			return if (other is Regular) {
				raw(mag % other.mag)
			} else if (other === Zero) {
				throw ArithmeticException("$this % 0")
			} else if (other === PositiveInfinity) {
				this
			} else if (other === NegativeInfinity) {
				-this
			} else {
				throw RuntimeException("Unreachable code")
			}
		}

		// Primitive operators
		override fun plus(other: Float): Decimal {
			if (other == 0f) {
				return this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this + NaN")
			} else if (other == Float.POSITIVE_INFINITY) {
				return PositiveInfinity
			} else if (other == Float.NEGATIVE_INFINITY) {
				return NegativeInfinity
			}

			return plus(valueOf(other))
		}

		override fun minus(other: Float): Decimal {
			if (other == 0f) {
				return this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this - NaN")
			} else if (other == Float.POSITIVE_INFINITY) {
				return NegativeInfinity
			} else if (other == Float.NEGATIVE_INFINITY) {
				return PositiveInfinity
			}

			return minus(valueOf(other))
		}

		override fun times(other: Float): Decimal {
			if (other == 1f) {
				return this
			} else if (other == 0f) {
				return Zero
			} else if (other == -1f) {
				return -this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this * NaN")
			} else if (other == Float.POSITIVE_INFINITY) {
				return if (signum() < 0) NegativeInfinity else PositiveInfinity
			} else if (other == Float.NEGATIVE_INFINITY) {
				return if (signum() < 0) PositiveInfinity else NegativeInfinity
			}

			return times(valueOf(other))
		}

		override fun div(other: Float): Decimal {
			if (other == 0f) {
				throw ArithmeticException("$this / 0")
			} else if (other == 1f) {
				return this
			} else if (other == -1f) {
				return -this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this / NaN")
			} else if (other.isInfinite()) {
				return Zero
			}

			return div(valueOf(other))
		}

		override fun plus(other: Double): Decimal {
			if (other == 0.0) {
				return this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this + NaN")
			} else if (other == Double.POSITIVE_INFINITY) {
				return PositiveInfinity
			} else if (other == Double.NEGATIVE_INFINITY) {
				return NegativeInfinity
			}

			return plus(valueOf(other))
		}

		override fun minus(other: Double): Decimal {
			if (other == 0.0) {
				return this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this - NaN")
			} else if (other == Double.POSITIVE_INFINITY) {
				return NegativeInfinity
			} else if (other == Double.NEGATIVE_INFINITY) {
				return PositiveInfinity
			}

			return minus(valueOf(other))
		}

		override fun times(other: Double): Decimal {
			if (other == 1.0) {
				return this
			} else if (other == 0.0) {
				return Zero
			} else if (other == -1.0) {
				return -this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this * NaN")
			} else if (other == Double.POSITIVE_INFINITY) {
				return if (signum() < 0) NegativeInfinity else PositiveInfinity
			} else if (other == Double.NEGATIVE_INFINITY) {
				return if (signum() < 0) PositiveInfinity else NegativeInfinity
			}

			return times(valueOf(other))
		}

		override fun div(other: Double): Decimal {
			if (other == 0.0) {
				throw ArithmeticException("$this / zero")
			} else if (other == 1.0) {
				return this
			} else if (other == -1.0) {
				return -this
			} else if (other.isNaN()) {
				throw ArithmeticException("$this / NaN")
			} else if (other.isInfinite()) {
				return Zero
			}

			return div(valueOf(other))
		}

		override fun plus(other: Int): Decimal {
			if (other == 0)
				return this

			return plus(valueOf(other))
		}

		override fun minus(other: Int): Decimal {
			if (other == 0)
				return this

			return minus(valueOf(other))
		}

		override fun times(other: Int): Decimal {
			if (other == 1) {
				return this
			} else if (other == 0) {
				return Zero
			} else if (other == -1) {
				return -this
			}

			return times(valueOf(other))
		}

		override fun div(other: Int): Decimal {
			if (other == 0) {
				throw ArithmeticException("$this / 0")
			} else if (other == 1) {
				return this
			} else if (other == -1) {
				return -this
			}

			return div(valueOf(other))
		}

		override fun plus(other: Long): Decimal {
			if (other == 0L)
				return this

			return plus(valueOf(other))
		}

		override fun minus(other: Long): Decimal {
			if (other == 0L)
				return this

			return minus(valueOf(other))
		}

		override fun times(other: Long): Decimal {
			if (other == 1L) {
				return this
			} else if (other == 0L) {
				return Zero
			} else if (other == -1L) {
				return -this
			}

			return times(valueOf(other))
		}

		override fun div(other: Long): Decimal {
			if (other == 0L) {
				throw ArithmeticException("$this / 0")
			} else if (other == 1L || isZero) {
				return this
			} else if (other == -1L) {
				return -this
			}

			return div(valueOf(other))
		}

		override fun plus(other: BigInteger): Decimal {
			if (other == BigInteger.ZERO)
				return this

			return plus(valueOf(other))
		}

		override fun minus(other: BigInteger): Decimal {
			if (other == BigInteger.ZERO)
				return this

			return minus(valueOf(other))
		}

		override fun times(other: BigInteger): Decimal {
			if (other == BigInteger.ONE) {
				return this
			} else if (other.signum() == 0) {
				return Zero
			} else if (other == BI_MINUS_ONE) {
				return -this
			}

			return times(valueOf(other))
		}

		override fun div(other: BigInteger): Decimal {
			if (other == BigInteger.ZERO) {
				throw ArithmeticException("$this / 0")
			} else if (other == BigInteger.ONE) {
				return this
			} else if (other == BI_MINUS_ONE) {
				return -this
			}

			return div(valueOf(other))
		}
		// /Primitive operators

		override fun unaryMinus(): Decimal {
			return Regular(-mag, null)
		}

		override fun toByteArray(): ByteArray {
			val result = mag.toByteArray()
			val copy = ByteArray(result.size + 1)
			copy[0] = TYPE_NORMAL
			result.copyInto(copy, 1)
			return copy
		}

		override fun floor(): Decimal {
			return Regular(whole)
		}

		override fun toString(decimals: Int): String {
			if (decimals == 0) {
				return whole.toString()
			}

			if (mag.signum() == 0) {
				return if (decimals < 0) {
					"0.0"
				} else {
					"0." + "0".repeat(decimals)
				}
			}

			var original = mag.toString()

			if (mag.signum() < 0) {
				// если у нас отрицательное число - убираем знак минуса
				original = original.substring(1)
			}

			if (original.length <= PRECISION) {
				// если у нас чисто дробное число - дописываем нули в начало
				original = "0".repeat(PRECISION - original.length + 1) + original
			}

			// теперь у нас беззнаковая строка с нужной длиной

			if (decimals < 0) {
				// нам неважно количество знаков после запятой
				var result = original.substring(0, original.length - PRECISION) + "." + original.substring(original.length - PRECISION)

				if (mag.signum() < 0) {
					result = "-$result"
				}

				var pos = result.length - 1

				while (result[pos] == '0') {
					if (result[pos - 1] == '0' || result[pos - 1] != '.')
						pos--
					else
						break
				}

				return result.substring(0, pos + 1)
			} else {
				// нужно некоторое количество знаков после запятой
				val result = original.substring(0, original.length - PRECISION) + "."
				var decimalPart = original.substring(original.length - PRECISION)

				if (decimalPart.length < decimals) {
					decimalPart += "0".repeat(decimals - decimalPart.length)
				} else if (decimalPart.length > decimals) {
					decimalPart = decimalPart.substring(0, decimals)
				}

				return if (mag.signum() < 0) {
					"-$result$decimalPart"
				} else {
					"$result$decimalPart"
				}
			}
		}

		override fun toBigDecmial(): BigDecimal {
			return BigDecimal(mag, PRECISION)
		}

		override fun equals(other: Any?): Boolean {
			return this === other || other is Regular && mag == other.mag
		}

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

	private object PositiveInfinity : Decimal() {
		private fun readResolve(): Any = PositiveInfinity

		override val isInfinite: Boolean
			get() = true
		override val isFinite: Boolean
			get() = false

		override fun equals(other: Any?): Boolean {
			return this === other
		}

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

		override fun compareTo(other: Decimal): Int {
			return if (other === this) 0 else 1
		}

		override fun toByte(): Byte {
			return Byte.MAX_VALUE
		}

		override fun toDouble(): Double {
			return Double.POSITIVE_INFINITY
		}

		override fun toFloat(): Float {
			return Float.POSITIVE_INFINITY
		}

		override fun toInt(): Int {
			return Int.MAX_VALUE
		}

		override fun toLong(): Long {
			return Long.MAX_VALUE
		}

		override fun toShort(): Short {
			return Short.MAX_VALUE
		}

		override val whole: BigInteger
			get() = throw UnsupportedOperationException("Attempt to get whole part of positive infinity")
		override val fractional: BigInteger
			get() = throw UnsupportedOperationException("Attempt to get fractional part of positive infinity")

		override fun plus(other: Decimal): Decimal {
			// not very mathematically correct, since
			// case "infinity + infinity" with either sign on either side
			// is undefined
			return this
		}

		override fun minus(other: Decimal): Decimal {
			// not very mathematically correct, since
			// case "infinity - infinity" with either sign on either side
			// is undefined
			return this
		}

		override fun times(other: Decimal): Decimal {
			return if (other.signum() == 0)
				Zero
			else if (other.signum() < 0)
				NegativeInfinity
			else
				this
		}

		override fun div(other: Decimal): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other === this)
				throw ArithmeticException("Dividing positive infinity by itself is undefined")
			else if (other === NegativeInfinity)
				throw ArithmeticException("Dividing positive infinity by negative infinity is undefined")
			else if (other.signum() < 0)
				NegativeInfinity
			else
				this
		}

		override fun rem(other: Decimal): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to remainder divide positive infinity by zero")
			else if (other.signum() < 0)
				NegativeInfinity
			else
				this
		}

		override fun plus(other: Float): Decimal {
			return this
		}

		override fun minus(other: Float): Decimal {
			return this
		}

		override fun times(other: Float): Decimal {
			return if (other == 0f)
				Zero
			else if (other < 0f)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun div(other: Float): Decimal {
			return if (other == 0f)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other < 0f)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun plus(other: Double): Decimal {
			return this
		}

		override fun minus(other: Double): Decimal {
			return this
		}

		override fun times(other: Double): Decimal {
			return if (other == 0.0)
				Zero
			else if (other < 0.0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun div(other: Double): Decimal {
			return if (other == 0.0)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other < 0.0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun plus(other: Int): Decimal {
			return this
		}

		override fun minus(other: Int): Decimal {
			return this
		}

		override fun times(other: Int): Decimal {
			return if (other == 0)
				Zero
			else if (other < 0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun div(other: Int): Decimal {
			return if (other == 0)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other < 0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun plus(other: Long): Decimal {
			return this
		}

		override fun minus(other: Long): Decimal {
			return this
		}

		override fun times(other: Long): Decimal {
			return if (other == 0L)
				Zero
			else if (other < 0L)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun div(other: Long): Decimal {
			return if (other == 0L)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other < 0L)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun plus(other: BigInteger): Decimal {
			return this
		}

		override fun minus(other: BigInteger): Decimal {
			return this
		}

		override fun times(other: BigInteger): Decimal {
			return if (other.signum() == 0)
				Zero
			else if (other.signum() < 0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun div(other: BigInteger): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to divide positive infinity by zero")
			else if (other.signum() < 0)
				NegativeInfinity
			else
				PositiveInfinity
		}

		override fun unaryMinus(): Decimal {
			return NegativeInfinity
		}

		override fun signum(): Int {
			return 1
		}

		override fun toByteArray(): ByteArray {
			return byteArrayOf(TYPE_POSITIVE_INFINITY)
		}

		override fun floor(): Decimal {
			return this
		}

		override fun toString(decimals: Int): String {
			return "+Infinity"
		}

		override fun toBigDecmial(): BigDecimal {
			throw UnsupportedOperationException("Unable to construct BigDecimal of positive infinity")
		}
	}

	private object NegativeInfinity : Decimal() {
		private fun readResolve(): Any = NegativeInfinity

		override val isInfinite: Boolean
			get() = true
		override val isFinite: Boolean
			get() = false

		override fun equals(other: Any?): Boolean {
			return this === other
		}

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

		override fun compareTo(other: Decimal): Int {
			return if (other === this) 0 else -1
		}

		override fun toByte(): Byte {
			return Byte.MIN_VALUE
		}

		override fun toDouble(): Double {
			return Double.NEGATIVE_INFINITY
		}

		override fun toFloat(): Float {
			return Float.NEGATIVE_INFINITY
		}

		override fun toInt(): Int {
			return Int.MIN_VALUE
		}

		override fun toLong(): Long {
			return Long.MIN_VALUE
		}

		override fun toShort(): Short {
			return Short.MIN_VALUE
		}

		override val whole: BigInteger
			get() = throw UnsupportedOperationException("Attempt to get whole part of negative infinity")
		override val fractional: BigInteger
			get() = throw UnsupportedOperationException("Attempt to get fractional part of negative infinity")

		override fun plus(other: Decimal): Decimal {
			return this
		}

		override fun minus(other: Decimal): Decimal {
			return this
		}

		override fun times(other: Decimal): Decimal {
			return if (other.signum() == 0)
				Zero
			else if (other.signum() < 0)
				PositiveInfinity
			else
				this
		}

		override fun div(other: Decimal): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to divide negative infinity by zero")
			else if (other === this)
				throw ArithmeticException("Dividing negative infinity by itself is undefined")
			else if (other === PositiveInfinity)
				throw ArithmeticException("Dividing negative infinity by positive infinity is undefined")
			else if (other.signum() < 0)
				PositiveInfinity
			else
				this
		}

		override fun rem(other: Decimal): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to remainder divide negative infinity by zero")
			else if (other.signum() < 0)
				PositiveInfinity
			else
				this
		}

		override fun plus(other: Float): Decimal {
			return this
		}

		override fun minus(other: Float): Decimal {
			return this
		}

		override fun times(other: Float): Decimal {
			return if (other == 0f)
				Zero
			else if (other < 0f)
				PositiveInfinity
			else
				this
		}

		override fun div(other: Float): Decimal {
			return if (other == 0f)
				throw ArithmeticException("Attempt to divide negative negative by zero")
			else if (other < 0f)
				PositiveInfinity
			else
				this
		}

		override fun plus(other: Double): Decimal {
			return this
		}

		override fun minus(other: Double): Decimal {
			return this
		}

		override fun times(other: Double): Decimal {
			return if (other == 0.0)
				Zero
			else if (other < 0.0)
				PositiveInfinity
			else
				this
		}

		override fun div(other: Double): Decimal {
			return if (other == 0.0)
				throw ArithmeticException("Attempt to divide negative infinity by zero")
			else if (other < 0.0)
				PositiveInfinity
			else
				this
		}

		override fun plus(other: Int): Decimal {
			return this
		}

		override fun minus(other: Int): Decimal {
			return this
		}

		override fun times(other: Int): Decimal {
			return if (other == 0)
				Zero
			else if (other < 0)
				PositiveInfinity
			else
				this
		}

		override fun div(other: Int): Decimal {
			return if (other == 0)
				throw ArithmeticException("Attempt to divide negative infinity by zero")
			else if (other < 0)
				PositiveInfinity
			else
				this
		}

		override fun plus(other: Long): Decimal {
			return this
		}

		override fun minus(other: Long): Decimal {
			return this
		}

		override fun times(other: Long): Decimal {
			return if (other == 0L)
				Zero
			else if (other < 0L)
				PositiveInfinity
			else
				this
		}

		override fun div(other: Long): Decimal {
			return if (other == 0L)
				throw ArithmeticException("Attempt to divide negative infinity by zero")
			else if (other < 0L)
				PositiveInfinity
			else
				this
		}

		override fun plus(other: BigInteger): Decimal {
			return this
		}

		override fun minus(other: BigInteger): Decimal {
			return this
		}

		override fun times(other: BigInteger): Decimal {
			return if (other.signum() == 0)
				Zero
			else if (other.signum() < 0)
				PositiveInfinity
			else
				this
		}

		override fun div(other: BigInteger): Decimal {
			return if (other.signum() == 0)
				throw ArithmeticException("Attempt to divide negative infinity by zero")
			else if (other.signum() < 0)
				PositiveInfinity
			else
				this
		}

		override fun unaryMinus(): Decimal {
			return PositiveInfinity
		}

		override fun signum(): Int {
			return -1
		}

		override fun toByteArray(): ByteArray {
			return byteArrayOf(TYPE_NEGATIVE_INFINITY)
		}

		override fun floor(): Decimal {
			return this
		}

		override fun toString(decimals: Int): String {
			return "-Infinity"
		}

		override fun toBigDecmial(): BigDecimal {
			throw UnsupportedOperationException("Unable to construct BigDecimal of negative infinity")
		}
	}

	private object Zero : Decimal() {
		private fun readResolve(): Any = Zero

		override val isInfinite: Boolean
			get() = false
		override val isFinite: Boolean
			get() = true

		override fun equals(other: Any?): Boolean {
			return this === other || other is Decimal && other.isZero
		}

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

		override fun compareTo(other: Decimal): Int {
			return -other.signum()
		}

		override fun toByte(): Byte {
			return 0
		}

		override fun toDouble(): Double {
			return 0.0
		}

		override fun toFloat(): Float {
			return 0f
		}

		override fun toInt(): Int {
			return 0
		}

		override fun toLong(): Long {
			return 0L
		}

		override fun toShort(): Short {
			return 0
		}

		override val whole: BigInteger
			get() = BigInteger.ZERO
		override val fractional: BigInteger
			get() = BigInteger.ZERO

		override fun plus(other: Decimal): Decimal {
			return other
		}

		override fun minus(other: Decimal): Decimal {
			return -other
		}

		override fun times(other: Decimal): Decimal {
			return this
		}

		override fun div(other: Decimal): Decimal {
			if (other === this)
				throw ArithmeticException("0 / 0")
			else
				return this
		}

		override fun rem(other: Decimal): Decimal {
			if (other === this)
				throw ArithmeticException("0 % 0")
			else
				return this
		}

		override fun plus(other: Float): Decimal {
			return valueOf(other)
		}

		override fun minus(other: Float): Decimal {
			return valueOf(-other)
		}

		override fun times(other: Float): Decimal {
			return this
		}

		override fun div(other: Float): Decimal {
			if (other == 0f)
				throw ArithmeticException("0 / 0")

			return this
		}

		override fun plus(other: Double): Decimal {
			return valueOf(other)
		}

		override fun minus(other: Double): Decimal {
			return valueOf(-other)
		}

		override fun times(other: Double): Decimal {
			return this
		}

		override fun div(other: Double): Decimal {
			if (other == 0.0)
				throw ArithmeticException("0 / 0")

			return this
		}

		override fun plus(other: Int): Decimal {
			return valueOf(other)
		}

		override fun minus(other: Int): Decimal {
			return valueOf(-other)
		}

		override fun times(other: Int): Decimal {
			return this
		}

		override fun div(other: Int): Decimal {
			if (other == 0)
				throw ArithmeticException("0 / 0")

			return this
		}

		override fun plus(other: Long): Decimal {
			return valueOf(other)
		}

		override fun minus(other: Long): Decimal {
			return valueOf(-other)
		}

		override fun times(other: Long): Decimal {
			return this
		}

		override fun div(other: Long): Decimal {
			if (other == 0L)
				throw ArithmeticException("0 / 0")

			return this
		}

		override fun plus(other: BigInteger): Decimal {
			return valueOf(other)
		}

		override fun minus(other: BigInteger): Decimal {
			return valueOf(-other)
		}

		override fun times(other: BigInteger): Decimal {
			return this
		}

		override fun div(other: BigInteger): Decimal {
			if (other == BigInteger.ZERO)
				throw ArithmeticException("0 / 0")

			return this
		}

		override fun unaryMinus(): Decimal {
			return this
		}

		override fun signum(): Int {
			return 0
		}

		override fun toByteArray(): ByteArray {
			return byteArrayOf(TYPE_ZERO)
		}

		override fun floor(): Decimal {
			return this
		}

		override fun toString(decimals: Int): String {
			if (decimals == 0)
				return "0"
			else if (decimals < 0)
				return "0.0"
			else
				return "0." + "0".repeat(decimals)
		}

		override fun toBigDecmial(): BigDecimal {
			return BigDecimal.ZERO
		}
	}

	@Suppress("unused")
	companion object {
		/**
		 * Amount of digits after fixed point, in 10 radix
		 */
		const val PRECISION = 11

		const val TYPE_ZERO: Byte = 0
		const val TYPE_NORMAL: Byte = 1
		const val TYPE_POSITIVE_INFINITY: Byte = 2
		const val TYPE_NEGATIVE_INFINITY: Byte = 3

		@JvmField
		val PRECISION_POW_BI: BigInteger = BigInteger("1" + "0".repeat(PRECISION))
		@JvmField
		val PRECISION_DOUBLE = PRECISION_POW_BI.toDouble()
		@JvmField
		val PRECISION_FLOAT = PRECISION_POW_BI.toFloat()
		@JvmField
		val PRECISION_POW_BI_NEGATIVE: BigInteger = -PRECISION_POW_BI
		@JvmField
		val PRECISION_POW_BI_HIGH: BigInteger = PRECISION_POW_BI / BigInteger.TWO

		private const val cacheSize = 16384
		private const val cacheSizeL = cacheSize.toLong()

		private val cache = Array<Decimal>(cacheSize) { Regular(BigInteger.valueOf(it.toLong() - cacheSize / 2)) }

		init {
			check(cache[cacheSize / 2].signum() == 0)
			cache[cacheSize / 2] = Zero
		}

		/**
		 * Returns pooled value if present, otherwise constructs new object
		 */
		@JvmStatic
		fun valueOf(value: Int): Decimal {
			if (value in (cacheSize / -2) until cacheSize / 2) {
				return cache[value + cacheSize / 2]
			}

			return Regular(BigInteger.valueOf(value.toLong()))
		}

		/**
		 * Returns pooled value if present, otherwise constructs new object
		 */
		@JvmStatic
		fun valueOf(value: Long): Decimal {
			if (value in (cacheSizeL / -2L) until cacheSizeL / 2L) {
				return cache[value.toInt() + cacheSize / 2]
			}

			return Regular(BigInteger.valueOf(value))
		}

		/**
		 * Returns pooled value if present, otherwise constructs new object
		 */
		@JvmStatic
		fun valueOf(value: Short) = valueOf(value.toInt())

		/**
		 * Returns pooled value if present, otherwise constructs new object
		 */
		@JvmStatic
		fun valueOf(value: Byte) = valueOf(value.toInt())

		@JvmStatic fun valueOf(value: BigInteger): Decimal {
			return if (value.signum() == 0) {
				Zero
			} else {
				Regular(value)
			}
		}

		@JvmStatic fun valueOf(value: BigDecimal): Decimal {
			return if (value.signum() == 0) {
				Zero
			} else {
				Regular(value)
			}
		}

		@JvmStatic fun valueOf(value: String): Decimal {
			if (value == "0") {
				return Zero
			} else {
				val lower = value.lowercase()

				// why not
				if (lower.startsWith("+inf") || lower.startsWith("inf") || lower == "∞" || lower == "+∞") {
					return PositiveInfinity
				} else if (lower.startsWith("-inf") || lower == "-∞") {
					return NegativeInfinity
				} else {
					val result = Regular(value)
					if (result.signum() == 0) return Zero
					return result
				}
			}
		}

		@JvmStatic fun valueOf(value: Float): Decimal {
			return if (value == 0f) {
				Zero
			} else if (value == Float.POSITIVE_INFINITY) {
				PositiveInfinity
			} else if (value == Float.NEGATIVE_INFINITY) {
				NegativeInfinity
			} else {
				Regular(value)
			}
		}

		@JvmStatic fun valueOf(value: Double): Decimal {
			return if (value == 0.0) {
				Zero
			} else if (value == Double.POSITIVE_INFINITY) {
				PositiveInfinity
			} else if (value == Double.NEGATIVE_INFINITY) {
				NegativeInfinity
			} else {
				Regular(value)
			}
		}

		@JvmStatic
		fun fromByteArray(input: ByteArray): Decimal {
			return if (input.isEmpty()) {
				Zero
			} else {
				return when (input[0]) {
					TYPE_NORMAL -> raw(BigInteger(input.copyOfRange(1, input.size)))
					TYPE_NEGATIVE_INFINITY -> if (input.size == 1) NegativeInfinity else Zero
					TYPE_POSITIVE_INFINITY -> if (input.size == 1) PositiveInfinity else Zero
					// TYPE_ZERO -> Zero
					else -> Zero
				}
			}
		}

		@JvmStatic
		fun deserializeNBT(input: Tag?): Decimal {
			if (input is ByteArrayTag) {
				return fromByteArray(input.asByteArray)
			} else if (input is StringTag) {
				return valueOf(input.asString)
			}

			return Zero
		}

		@JvmStatic
		fun read(buff: FriendlyByteBuf): Decimal {
			return fromByteArray(buff.readByteArray())
		}

		/**
		 * Takes in [value] as-is and constructs [Decimal] out of it ([value] is treated as already scaled by [PRECISION])
		 */
		@JvmStatic
		fun raw(value: BigInteger): Decimal {
			return if (value.signum() == 0) {
				Zero
			} else {
				Regular(value, null)
			}
		}

		@JvmField val POSITIVE_INFINITY: Decimal = PositiveInfinity
		@JvmField val NEGATIVE_INFINITY: Decimal = NegativeInfinity
		@JvmField val ZERO: Decimal = Zero
		@JvmField val ONE = valueOf(1)
		@JvmField val TWO = valueOf(2)
		@JvmField val TEN = valueOf(10)
		@JvmField val MINUS_ONE = valueOf(-1)
		@JvmField val MINUS_TWO = valueOf(-2)
		@JvmField val MINUS_TEN = valueOf(-10)
		@JvmField val ONE_TENTH = valueOf("0.1")
		@JvmField val ONE_QUARTER = valueOf("0.25")
		@JvmField val ONE_EIGHTH = valueOf("0.125")
		@JvmField val ONE_HALF = valueOf("0.5")
		@JvmField val MINUS_ONE_TENTH = valueOf("-0.1")
		@JvmField val MINUS_ONE_EIGHTH = valueOf("-0.125")
		@JvmField val MINUS_ONE_QUARTER = valueOf("-0.25")
		@JvmField val MINUS_ONE_HALF = valueOf("-0.5")

		@JvmField val INT_MAX_VALUE: Decimal = Regular(BI_INT_MAX)
		@JvmField val INT_MIN_VALUE: Decimal = Regular(BI_INT_MIN)
		@JvmField val LONG_MAX_VALUE: Decimal = Regular(BI_LONG_MAX)
		@JvmField val LONG_MIN_VALUE: Decimal = Regular(BI_LONG_MIN)

		@JvmField val FLOAT_MAX_VALUE: Decimal = Regular(BI_FLOAT_MAX)
		@JvmField val FLOAT_MIN_VALUE: Decimal = Regular(BI_FLOAT_MIN)
		@JvmField val DOUBLE_MAX_VALUE: Decimal = Regular(BI_DOUBLE_MAX)
		@JvmField val DOUBLE_MIN_VALUE: Decimal = Regular(BI_DOUBLE_MIN)
	}
}

fun FriendlyByteBuf.readDecimal() = Decimal.read(this)
fun FriendlyByteBuf.writeDecimal(value: Decimal) = value.write(this)

fun InputStream.readDecimal(): Decimal {
	val size = readVarIntLE()
	require(size >= 0) { "Negative payload size: $size" }
	val bytes = ByteArray(size)
	read(bytes)
	return Decimal.fromByteArray(bytes)
}

fun OutputStream.writeDecimal(value: Decimal) {
	val bytes = value.toByteArray()
	writeVarIntLE(bytes.size)
	write(bytes)
}

fun CompoundTag.getDecimal(key: String) = Decimal.deserializeNBT(this[key])
fun CompoundTag.putDecimal(key: String, value: Decimal) = put(key, value.serializeNBT())

operator fun CompoundTag.set(key: String, value: Decimal) = putDecimal(key, value)

fun Float.toDecimal() = Decimal(this)
fun Double.toDecimal() = Decimal(this)
fun Int.toDecimal() = Decimal(this)
fun Byte.toDecimal() = Decimal(this)
fun Short.toDecimal() = Decimal(this)
fun Long.toDecimal() = Decimal(this)
fun Decimal.toDecimal() = this

class DecimalConfigValue(
	parent: ModConfigSpec.ConfigValue<String>,
	val minimum: Decimal? = null,
	val maximum: Decimal? = null,
) : ObservedConfigValue<Decimal>(parent) {
	override fun fromString(value: String): Decimal? {
		try {
			val parsed = Decimal(value)

			if (minimum != null && minimum > parsed) {
				return minimum
			} else if (maximum != null && maximum < parsed) {
				return maximum
			}

			return parsed
		} catch (err: java.lang.NumberFormatException) {
			return null
		}
	}

	override fun toString(value: Decimal): Pair<String, Decimal> {
		if (minimum != null && minimum > value) {
			return minimum.toString() to minimum
		} else if (maximum != null && maximum < value) {
			return maximum.toString() to maximum
		}

		return value.toString() to value
	}
}

private fun ModConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) {
	if (minimum != null && maximum != null) {
		comment("Range: $minimum ~ $maximum")
	} else if (minimum != null) {
		comment("Range: >= $minimum")
	} else if (maximum != null) {
		comment("Range: <= $maximum")
	}
}

fun ModConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
	commentRange(minimum, maximum)
	comment("Default: $defaultValue")
	return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum)
}

fun ModConfigSpec.Builder.defineDecimal(path: List<String>, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue {
	commentRange(minimum, maximum)
	comment("Default: $defaultValue")
	return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum)
}

fun RandomSource.nextDecimal(min: Decimal, max: Decimal, round: Boolean = false): Decimal {
	val value = nextDouble()

	return if (round) {
		Decimal((min + (max - min) * value).whole)
	} else {
		min + (max - min) * value
	}
}

fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal {
	return nextDecimal(-value / 2, value / 2, round)
}
