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

import it.unimi.dsi.fastutil.chars.CharArrayList
import net.minecraft.network.chat.Component
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.isNegative
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger
import java.util.function.BooleanSupplier
import kotlin.math.absoluteValue
import kotlin.math.roundToInt

private fun concat(numbers: String, suffix: Any): Component {
	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)
	}

	return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix)
}

private val never = BooleanSupplier { false }

private fun reformat(numbers: String): String {
	if (numbers.length <= 4)
		return numbers

	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])
	}

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

fun Long.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component {
	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): Component {
	return toLong().formatSiComponent(suffix, decimalPlaces, formatAsReadable, bias)
}

fun Double.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component {
	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): Component {
	require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
	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"
	}
}
