package ru.dbotthepony.mc.otm.core.util

import com.mojang.datafixers.util.Either
import it.unimi.dsi.fastutil.chars.CharArrayList
import it.unimi.dsi.fastutil.floats.Float2ObjectArrayMap
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.inventory.tooltip.TooltipComponent
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.client.render.ChartLevelLabels
import ru.dbotthepony.mc.otm.client.render.ChartTooltipElement
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.clusterize
import ru.dbotthepony.mc.otm.core.math.isNegative
import ru.dbotthepony.mc.otm.core.math.isZero
import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget
import java.math.BigInteger
import java.util.function.BooleanSupplier
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.roundToInt

private fun concat(numbers: String, suffix: Any): MutableComponent {
	if (suffix == "")
		return TextComponent(numbers)
	else if (suffix is Component)
		return TextComponent(numbers + " " + suffix.string)
	else
		return TextComponent("$numbers $suffix")
}

fun BigInteger.formatReadableNumber(): String {
	if (isZero) {
		return "0"
	}

	val strValue = toString()

	val absLength = strValue.length - (if (isNegative) 1 else 0)
	val remainder = absLength % 3
	var groups = absLength / 3

	if (remainder == 0) {
		groups--
	}

	val buffer = CharArray((if (remainder == 0) 3 else remainder) + groups * 4 + if (isNegative) 1 else 0)
	var c = 0
	var index = buffer.size - 1

	for (i in strValue.length - 1 downTo (if (isNegative) 1 else 0)) {
		c++

		if (c == 4) {
			buffer[index] = ' '
			index--
			c = 1
		}

		buffer[index] = strValue[i]
		index--
	}

	if (isNegative) {
		buffer[index] = '-'
	}

	return String(buffer)
}

fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never): Component {
	require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
	val prefix = SiPrefix.determine(this)
	if (prefix.isEmpty) return concat(toString(decimalPlaces), suffix)
	val isNegative = isNegative
	val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.bigInteger)
	val divided = arr[0].toString()
	val remainder = arr[1].toString()

	if (decimalPlaces == 0) {
		if (isNegative) {
			return TranslatableComponent(prefix.formatLocaleKey, "-$divided${prefix.symbol}", suffix)
		} else {
			return TranslatableComponent(prefix.formatLocaleKey, divided + prefix.symbol, suffix)
		}
	}

	@Suppress("NAME_SHADOWING")
	val decimalPlaces = decimalPlaces.coerceAtMost(prefix.power)
	val add = (if (isNegative) 1 else 0)
	val buffer = CharArray(divided.length + 1 + decimalPlaces + add)

	if (isNegative) {
		buffer[0] = '-'
	}

	for (i in divided.indices) {
		buffer[add + i] = divided[i]
	}

	buffer[add + divided.length] = '.'

	for (i in 0 until decimalPlaces) {
		buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i)
	}

	if (suffix == "")
		return TranslatableComponent(prefix.conciseFormatLocaleKey, String(buffer))
	else
		return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix)
}

private val never = BooleanSupplier { false }

private fun reformat(numbers: String): String {
	@Suppress("NAME_SHADOWING")
	var numbers = numbers

	if (numbers.isEmpty() || numbers.length <= 4)
		return numbers

	val negative = numbers[0] == '-'

	if (negative && numbers.length == 5)
		return numbers
	else if (negative)
		numbers = numbers.substring(1)

	val result = CharArrayList((numbers.length * 1.6).roundToInt())

	var dot = numbers.lastIndexOf('.')

	if (dot != -1) {
		for (i in numbers.length - 1 downTo dot) {
			result.add(numbers[i])
		}

		dot--
	} else {
		dot = numbers.length - 1
	}

	var c = 0

	for (i in dot downTo 0) {
		if (++c == 4) {
			c = 1
			result.add(' ')
		}

		result.add(numbers[i])
	}

	if (negative)
		result.add('-')

	return String(CharArray(result.size) {
		result.getChar(result.size - it - 1)
	})
}

// because minecraft's font 'Figure Space' (FSP) does not match width of numbers
private fun resplice(number: String, decimals: Int): String {
	var i = if (decimals != 0) decimals + 4 else 3
	val resplice = number.toCharArray()

	while (i < number.length) {
		resplice[resplice.size - i - 1] = ' '
		i += 3
	}

	return String(resplice)
}

fun Long.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): MutableComponent {
	require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }

	if (formatAsReadable.asBoolean) {
		if (bias == 0) {
			return concat(reformat(toString()), suffix)
		} else {
			val prefix = SiPrefix.NONE.neighbour(bias)
			return TranslatableComponent(prefix.formatLocaleKey, reformat(toString()), suffix)
		}
	}

	val prefix = SiPrefix.determine(this)
	return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, "%.${decimalPlaces}f".format(this.toDouble() / prefix.long!!.toDouble()), suffix)
}

fun Int.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): MutableComponent {
	return toLong().formatSiComponent(suffix, decimalPlaces, formatAsReadable, bias)
}

fun Double.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): MutableComponent {
	require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
	if (formatAsReadable.asBoolean && this.absoluteValue >= 1.0) return concat(reformat("%.${decimalPlaces}f".format(this)), suffix)
	val prefix = SiPrefix.determine(this)
	return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, "%.${decimalPlaces}f".format(this / prefix.double), suffix)
}

fun Decimal.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): MutableComponent {
	require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
	if (this == Decimal.POSITIVE_INFINITY) return concat("∞", suffix)
	if (this == Decimal.NEGATIVE_INFINITY) return concat("-∞", suffix)
	if (formatAsReadable.asBoolean && this.absoluteValue >= Decimal.ONE) return concat(reformat(toString(decimalPlaces)), suffix)
	val prefix = SiPrefix.determine(this)
	return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, (this / prefix.decimal).toString(decimalPlaces), suffix)
}

fun Int.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Int.formatFluid(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.fluid.name"), decimalPlaces, formatAsReadable = formatAsReadable, bias = -1)
fun Decimal.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Decimal.formatMatter(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Decimal.formatMatterFull(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable))

fun BigInteger.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun BigInteger.formatMatter(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun BigInteger.formatMatterFull(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable))

fun formatPowerLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatPower(decimalPlaces, formatAsReadable = formatAsReadable), b.formatPower(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatMatterLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable), b.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatFluidLevel(a: Int, b: Int, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatFluidLevel(a: Int, b: Int, name: Component, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.fluid.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), name)

private fun padded(num: Int): String {
	if (num in 0 .. 9) {
		return "0$num"
	}

	return num.toString()
}

fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String {
	@Suppress("name_shadowing")
	var leftTicks = ticks

	// val mTicks = leftTicks % 20
	leftTicks /= 20

	val seconds = padded(leftTicks % 60)
	leftTicks /= 60

	if (longFormat) {
		if (ticks <= 0) {
			return "00:00:00"
		} else if (ticks <= 20) {
			return ".00000${padded(ticks)}"
		}

		val minutes = padded(leftTicks % 60)
		leftTicks /= 60
		val hours = padded(leftTicks)
		return "$hours:$minutes:$seconds"
	} else {
		if (ticks <= 0) {
			return "00:00"
		} else if (ticks <= 20) {
			return ".00${padded(ticks)}"
		}

		val minutes = leftTicks

		if (minutes > 99) {
			return "**:**"
		}

		return "${padded(minutes)}:$seconds"
	}
}

/**
 * Clientside only function, do not call on shared/server realm.
 */
fun formatHistory(
	result: MutableList<Either<FormattedText, TooltipComponent>>,
	widget: IProfiledLevelGaugeWidget,
	bias: Int = 0,
	decimals: Int = 3,
	verbose: BooleanSupplier = never,
	suffix: Any = "",
) {
	if (!ClientConfig.GUI.CHARTS_IN_TOOLTIPS) {
		formatHistoryLines(result, widget, bias, decimals, verbose, suffix)
	} else {
		formatHistoryChart(result, widget, bias, decimals, verbose, suffix)
	}
}

private fun formatHistoryChart(
	result: MutableList<Either<FormattedText, TooltipComponent>>,
	widget: IProfiledLevelGaugeWidget,
	bias: Int = 0,
	decimals: Int = 3,
	verbose: BooleanSupplier = never,
	suffix: Any = "",
) {
	val diff = Array(widget.received.width) { widget.received[it] - widget.transferred[it] }
	val normalizedDiff: FloatArray
	val received: FloatArray
	val transferred: FloatArray
	val labels: ChartLevelLabels

	val hasPositiveInfinity = diff.any { it.isInfinite && it.isPositive }
	val hasNegativeInfinity = diff.any { it.isInfinite && it.isNegative }

	if (hasPositiveInfinity && hasNegativeInfinity) {
		normalizedDiff = FloatArray(diff.size) {
			if (widget.received[it].isInfinite && widget.transferred[it].isInfinite)
				0.5f
			else if (diff[it].isInfinite && diff[it].isNegative)
				0f
			else if (diff[it].isInfinite && diff[it].isPositive)
				1f
			else
				0.5f
		}

		received = FloatArray(diff.size) {
			if (widget.received[it].isInfinite) 1f else 0.5f
		}

		transferred = FloatArray(diff.size) {
			if (widget.transferred[it].isInfinite) 0f else 0.5f
		}

		labels = ChartLevelLabels(
			labels = mapOf(
				0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
				0f to TextComponent("-∞"),
				1f to TextComponent("∞"),
			)
		)
	} else if (hasPositiveInfinity) {
		normalizedDiff = FloatArray(diff.size) {
			if (diff[it].isInfinite)
				1f
			else
				0f
		}

		received = FloatArray(diff.size) {
			if (widget.received[it].isInfinite) 0.9f else 0.5f
		}

		transferred = FloatArray(diff.size)
		transferred.fill(0.5f)

		labels = ChartLevelLabels(
			labels = mapOf(
				0f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
				1f to TextComponent("∞"),
			)
		)
	} else if (hasNegativeInfinity) {
		normalizedDiff = FloatArray(diff.size) {
			if (diff[it].isInfinite)
				0f
			else
				1f
		}

		received = FloatArray(diff.size)
		received.fill(0.5f)

		transferred = FloatArray(diff.size) {
			if (widget.transferred[it].isInfinite) 0.1f else 0.5f
		}

		labels = ChartLevelLabels(
			labels = mapOf(
				1f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
				0f to TextComponent("-∞"),
			)
		)
	} else {
		val maxTransferred = widget.transferred.maxOrNull() ?: Decimal.ZERO
		val maxReceived = widget.received.maxOrNull() ?: Decimal.ZERO
		val labelNames = Float2ObjectArrayMap<Component>()

		if (maxTransferred.isZero && maxReceived.isZero) {
			normalizedDiff = FloatArray(diff.size)
			normalizedDiff.fill(0.5f)

			received = FloatArray(diff.size)
			received.fill(0.5f)

			transferred = FloatArray(diff.size)
			transferred.fill(0.5f)

			labelNames[0f] = TextComponent("-∞")
			labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
			labelNames[1f] = TextComponent("∞")
		} else {
			val zero: Float
			val transferredMult: Float
			val receivedMult: Float

			if (maxTransferred == maxReceived) {
				zero = 0.5f
				transferredMult = 0.5f
				receivedMult = 0.5f
			} else if (maxTransferred > maxReceived) {
				val ratio = (maxReceived / maxTransferred).toFloat()

				receivedMult = ratio * 0.5f
				zero = 1f - receivedMult
				transferredMult = 1f - receivedMult
			} else {
				val ratio = (maxTransferred / maxReceived).toFloat()

				transferredMult = ratio * 0.5f
				zero = transferredMult
				receivedMult = 1f - transferredMult
			}

			normalizedDiff = FloatArray(diff.size) {
				if (diff[it].isZero) {
					zero
				} else if (diff[it].isPositive) {
					zero + (diff[it] / maxReceived).toFloat() * receivedMult
				} else {
					zero + (diff[it] / maxTransferred).toFloat() * transferredMult
				}
			}

			if (maxReceived.isNotZero) {
				received = FloatArray(diff.size) {
					zero + (widget.received[it] / maxReceived).toFloat() * receivedMult
				}
			} else {
				received = FloatArray(diff.size)
				received.fill(1f)
			}

			if (maxTransferred.isNotZero) {
				transferred = FloatArray(diff.size) {
					zero + (widget.transferred[it] / maxTransferred).toFloat() * -transferredMult
				}
			} else {
				transferred = FloatArray(diff.size)
				transferred.fill(0f)
			}

			if (maxTransferred.isNotZero && maxReceived.isNotZero)
				labelNames[zero] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)

			labelNames[0f] = (-maxTransferred).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
			labelNames[1f] = maxReceived.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)

			val rand = java.util.Random()

			if (maxTransferred.isNotZero && transferredMult > 0.2f) {
				for (cluster in widget.transferred.clusterize(rand, zeroBound = true)) {
					val perc = (cluster.center / maxTransferred).toFloat() * transferredMult

					if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
						labelNames[perc] = (-cluster.center).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
				}
			}

			if (maxReceived.isNotZero && receivedMult > 0.2f) {
				for (cluster in widget.received.clusterize(rand, zeroBound = true)) {
					val perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult

					if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
						labelNames[perc] = cluster.center.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
				}
			}

			val clusters = diff.asIterable().clusterize(rand, zeroBound = true)

			for (cluster in clusters) {
				val perc: Float

				if (cluster.center.isZero)
					continue
				else if (cluster.center.isPositive)
					perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult
				else
					perc = (1f + (cluster.center / maxTransferred).toFloat()) * transferredMult

				if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
					labelNames[perc] = cluster.center.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
			}

			/*
			if ((zero - 0.5f).absoluteValue >= 0.25f) {
				// если "ноль" далёк от центра графика, то надо попробовать добавить пограничную направляющую
				if (maxReceived > maxTransferred) {
					// пограничная на приём
					val min = widget.received.minOrNull() ?: Decimal.ZERO
					val perc = (min / maxReceived).toFloat()

					if (min.isNotZero && perc < 0.92f) {
						labelNames[zero + perc * receivedMult] = min.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
					}
				} else if (maxTransferred.isNotZero) {
					// пограничная на отдачу
					val min = widget.transferred.minOrNull() ?: Decimal.ZERO
					val perc = (min / maxTransferred).toFloat()

					if (min.isNotZero && perc < 0.92f) {
						labelNames[perc * transferredMult] = (-min).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
					}
				}
			} else {
				// если нет, то надо попробовать добавить половинчатые направляющие
				if (transferredMult > 0.3f && (maxReceived.isZero || verbose.asBoolean && maxTransferred.isNotZero))
					labelNames[transferredMult * 0.5f] = (-maxTransferred * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)

				if (receivedMult > 0.3f && (maxTransferred.isZero || verbose.asBoolean && maxReceived.isNotZero))
					labelNames[zero + receivedMult * 0.5f] = (maxReceived * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
			}
			 */
		}

		labels = ChartLevelLabels(labels = labelNames)
	}

	val charts: List<Pair<FloatArray, RGBAColor>>
	val receivedActivity = widget.received.any { it.isNotZero }
	val transferredActivity = widget.received.any { it.isNotZero }

	if (!receivedActivity && transferredActivity) {
		charts = listOf(
			received to RGBAColor.YELLOW,
			normalizedDiff to RGBAColor.WHITE,
			transferred to RGBAColor.RED,
		)
	} else if (!transferredActivity && receivedActivity) {
		charts = listOf(
			transferred to RGBAColor.RED,
			normalizedDiff to RGBAColor.WHITE,
			received to RGBAColor.YELLOW,
		)
	} else {
		charts = listOf(
			received to RGBAColor.YELLOW,
			transferred to RGBAColor.RED,
			normalizedDiff to RGBAColor.WHITE,
		)
	}

	result.add(Either.left(TextComponent("")))

	run {
		val incoming = widget.received[0]
		val outgoing = widget.transferred[0]
		val delta = incoming - outgoing

		val deltaColor = if (delta.isZero) ChatFormatting.GRAY else if (delta.isPositive) ChatFormatting.DARK_GREEN else ChatFormatting.DARK_RED

		result.add(Either.left(TranslatableComponent(
			"otm.gui.diff",
			delta.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(deltaColor),
			incoming.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_GREEN),
			outgoing.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_RED),
		)))
	}

	run {
		val incoming = widget.received.calculateAverage()
		val outgoing = widget.transferred.calculateAverage()
		val delta = incoming - outgoing

		val deltaColor = if (delta.isZero) ChatFormatting.GRAY else if (delta.isPositive) ChatFormatting.DARK_GREEN else ChatFormatting.DARK_RED

		result.add(Either.left(TranslatableComponent(
			"otm.gui.diff",
			delta.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(deltaColor),
			incoming.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_GREEN),
			outgoing.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_RED),
		)))
	}

	result.add(Either.right(ChartTooltipElement(charts, if (verbose.asBoolean) 200f else 100f, if (verbose.asBoolean) 120f else 60f, levelLabels = labels)))
}

private fun formatHistoryLines(
	result: MutableList<Either<FormattedText, TooltipComponent>>,
	widget: IProfiledLevelGaugeWidget,
	bias: Int = 0,
	decimals: Int = 3,
	verbose: BooleanSupplier = never,
	suffix: Any = "",
) {
	data class Part(val formatted: String, val number: Decimal, val prefix: SiPrefix)
	data class Line(val delta: Part, val incoming: Part, val outgoing: Part)

	val lines = ArrayList<Line>()

	fun part(number: Decimal): Part {
		if (verbose.asBoolean && number.absoluteValue >= Decimal.ONE) {
			return Part(reformat(number.toString(decimals)), number, SiPrefix.NONE)
		}

		val prefix = SiPrefix.determine(number).neighbour(bias)
		if (prefix.isEmpty) return Part(number.toString(decimals), number, prefix)
		return Part((number / prefix.decimal).toString(decimals), number, prefix)
	}

	fun addLine(a: Decimal, b: Decimal) {
		lines.add(Line(part(a - b), part(a), part(b)))
	}

	if (verbose.asBoolean) {
		addLine(widget.receivedThisTick, widget.transferredThisTick)
	}

	addLine(
		widget.received.calculateAverage(),
		widget.transferred.calculateAverage()
	)

	if (verbose.asBoolean) {
		for (i in 0 until 20) {
			addLine(widget.received[i], widget.transferred[i])
		}
	}

	val maxWidthDeltaNumber = lines.maxOf { it.delta.formatted.length }
	val maxWidthInNumber = lines.maxOf { it.incoming.formatted.length }
	val maxWidthOutNumber = lines.maxOf { it.outgoing.formatted.length }

	val maxWidthDeltaSi = lines.maxOf { it.delta.prefix.length }
	val maxWidthInSi = lines.maxOf { it.incoming.prefix.length }
	val maxWidthOutSi = lines.maxOf { it.outgoing.prefix.length }

	fun Part.format(widthNumbers: Int, widthSi: Int): MutableComponent {
		return TranslatableComponent(
			prefix.formatLocaleKey,
			resplice("_".repeat(max(widthNumbers - formatted.length, 0)) + formatted, decimals) + "_".repeat(max(widthSi - prefix.length, 0)),
			suffix
		)
	}

	fun Line.format(): Component {
		val deltaColor = if (delta.number.isZero) ChatFormatting.GRAY else if (delta.number.isPositive) ChatFormatting.DARK_GREEN else ChatFormatting.DARK_RED

		return TranslatableComponent(
			"otm.gui.diff",
			delta.format(maxWidthDeltaNumber, maxWidthDeltaSi).withStyle(deltaColor),
			incoming.format(maxWidthInNumber, maxWidthInSi).withStyle(ChatFormatting.DARK_GREEN),
			outgoing.format(maxWidthOutNumber, maxWidthOutSi).withStyle(ChatFormatting.DARK_RED),
		)
	}

	result.add(Either.left(TextComponent("")))
	result.add(Either.left(lines.removeFirst().format()))

	if (verbose.asBoolean) {
		result.add(Either.left(TextComponent("---")))
		result.add(Either.left(lines.removeFirst().format()))
		result.add(Either.left(TextComponent("---")))
		lines.forEach { result.add(Either.left(it.format())) }
	}
}
