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

import com.google.common.collect.ImmutableList
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger
import kotlin.math.absoluteValue

enum class SiPrefix(val power: Int, val symbol: String) {
	YOCTO(-8,   "y"),
	ZEPTO(-7,   "z"),
	ATTO (-6,   "a"),
	FEMTO(-5,   "f"),
	PICO (-4,   "p"),
	NANO (-3,   "n"),
	MICRO(-2,   "μ"),
	MILLI(-1,   "m"),

	NONE(0,      "") {
		override val isEmpty: Boolean
			get() = true
	},

	KILO (1,    "k"),
	MEGA (2,    "M"),
	GIGA (3,    "G"),
	TERA (4,    "T"),
	PETA (5,    "P"),
	EXA  (6,    "E"),
	ZETTA(7,    "Z"),
	YOTTA(8,    "Y");

	open val isEmpty: Boolean get() = false

	val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern()
	val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern()

	val string: String

	init {
		if (power == 0) {
			string = "1"
		} else if (power < 0) {
			string = "0." + "0".repeat(power.absoluteValue * 3 - 1) + "1"
		} else {
			string = "1" + "0".repeat(power.absoluteValue * 3)
		}
	}

	fun paddedIndex(input: String, index: Int): Char {
		val finalIndex = input.length - power.absoluteValue * 3 + index

		if (finalIndex >= 0) {
			return input[finalIndex]
		}

		return '0'
	}

	val decimal = Decimal(string)
	val bigInteger = if (power >= 0) BigInteger(string) else null

	val long = if (power >= 0) string.toLongOrNull() else null
	val double = string.toDouble()

	fun neighbour(bias: Int): SiPrefix {
		if (bias == 0) {
			return this
		} else {
			val new = ordinal + bias

			if (new < 0) {
				return YOCTO
			} else if (new >= VALUES.size) {
				return YOTTA
			} else {
				return VALUES[new]
			}
		}
	}

	companion object {
		@JvmField
		val VALUES: ImmutableList<SiPrefix> = ImmutableList.copyOf(values())

		@JvmField
		val MULTIPLIES: ImmutableList<SiPrefix> = ImmutableList.builder<SiPrefix>()
			.add(NONE)
			.add(KILO)
			.add(MEGA)
			.add(GIGA)
			.add(TERA)
			.add(PETA)
			.add(EXA)
			.add(ZETTA)
			.add(YOTTA)
			.build()

		@JvmField
		val DECIMALS: ImmutableList<SiPrefix> = ImmutableList.builder<SiPrefix>()
			.add(NONE)
			.add(MILLI)
			.add(MICRO)
			.add(NANO)
			.add(PICO)
			.add(FEMTO)
			.add(ATTO)
			.add(ZEPTO)
			.add(YOCTO)
			.build()

		fun determine(value: Int, bias: Int = 0): SiPrefix {
			return determine(value.toLong(), bias)
		}

		fun determine(value: Long, bias: Int = 0): SiPrefix {
			val num = value.absoluteValue
			if (num <= 1L) return NONE
			return MULTIPLIES.last { it.long != null && it.long <= num }.neighbour(bias)
		}

		fun determine(value: Decimal, bias: Int = 0): SiPrefix {
			if (value.isZero) {
				return NONE
			}

			val num = value.absoluteValue

			if (num >= Decimal.ONE) {
				return (MULTIPLIES.lastOrNull { it.decimal <= num } ?: NONE).neighbour(bias)
			} else {
				return (DECIMALS.lastOrNull { it.decimal >= num } ?: NONE).neighbour(bias)
			}
		}

		fun determine(value: Double, bias: Int = 0): SiPrefix {
			if (value == 0.0) return NONE
			val num = value.absoluteValue

			if (num >= 1.0) {
				return (MULTIPLIES.lastOrNull { it.double <= num } ?: NONE).neighbour(bias)
			} else {
				return (DECIMALS.lastOrNull { it.double >= num } ?: NONE).neighbour(bias)
			}
		}

		fun determine(value: BigInteger, bias: Int = 0): SiPrefix {
			if (value.isZero) return NONE
			val num = value.abs()
			return (MULTIPLIES.lastOrNull { it.bigInteger!! <= num } ?: NONE).neighbour(bias)
		}
	}
}

