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

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.chars.CharAVLTreeSet
import net.minecraft.ChatFormatting
import kotlin.math.roundToInt

class HSVColor(hue: Float, saturation: Float, value: Float) : Comparable<HSVColor> {
	val hue = (hue % 360f).let { if (it < 0f) it + 360f else it }
	val saturation = saturation.coerceIn(0f, 1f)
	val value = value.coerceIn(0f, 1f)

	override fun equals(other: Any?): Boolean {
		return other === this || other is HSVColor && other.hue == hue && other.saturation == saturation && other.value == value
	}

	override fun hashCode(): Int {
		return hue.hashCode() + saturation.hashCode() * 31 + value.hashCode() * 31 * 31
	}

	fun copy(hue: Float = this.hue, saturation: Float = this.saturation, value: Float = this.value): HSVColor {
		return HSVColor(hue, saturation, value)
	}

	operator fun component1() = hue
	operator fun component2() = saturation
	operator fun component3() = value

	fun toRGBA(alpha: Float = 1f): RGBAColor {
		val valueMin = (1f - saturation) * value
		val delta = (value - valueMin) * (hue % 60f) / 60f
		val valueInc = valueMin + delta
		val valueDec = value - delta

		return when ((hue / 60f).toInt()) {
			0 -> RGBAColor(value, valueInc, valueMin, alpha)
			1 -> RGBAColor(valueDec, value, valueMin, alpha)
			2 -> RGBAColor(valueMin, value, valueInc, alpha)
			3 -> RGBAColor(valueMin, valueDec, value, alpha)
			4 -> RGBAColor(valueInc, valueMin, value, alpha)
			5 -> RGBAColor(value, valueMin, valueDec, alpha)
			else -> throw IllegalStateException("whut")
		}
	}

	override fun compareTo(other: HSVColor): Int {
		return comparator.compare(this, other)
	}

	companion object {
		@JvmField val WHITE = HSVColor(0f, 1f, 1f)

		private val comparator = Comparator
			.comparing(HSVColor::hue)
			.thenComparing(HSVColor::saturation)
			.thenComparing(HSVColor::value)
	}
}

private fun hex(value: Int): String {
	require(value in 0 .. 255)
	val v = value.toString(16)

	if (v.length == 1)
		return "0$v"
	else
		return v
}

class RGBAColor(red: Float, green: Float, blue: Float, alpha: Float = 1f) : Comparable<RGBAColor> {
	constructor(r: Int, g: Int, b: Int) : this((r / 255f), (g / 255f), (b / 255f), 1f)
	constructor(r: Int, g: Int, b: Int, a: Int) : this((r / 255f), (g / 255f), (b / 255f), (a / 255f))
	constructor(r: Int, g: Int, b: Int, a: Float) : this((r / 255f), (g / 255f), (b / 255f), a)

	val red = red.coerceIn(0f, 1f)
	val green = green.coerceIn(0f, 1f)
	val blue = blue.coerceIn(0f, 1f)
	val alpha = alpha.coerceIn(0f, 1f)

	val redInt get() = (red * 255f).roundToInt()
	val greenInt get() = (green * 255f).roundToInt()
	val blueInt get() = (blue * 255f).roundToInt()
	val alphaInt get() = (alpha * 255f).roundToInt()

	fun toRGBA(): Int {
		return (redInt shl 24) or (greenInt shl 16) or (blueInt shl 8) or alphaInt
	}

	fun toARGB(): Int {
		return (alphaInt shl 24) or (redInt shl 16) or (greenInt shl 8) or blueInt
	}

	fun toBGRA(): Int {
		return (blueInt shl 24) or (greenInt shl 16) or (redInt shl 8) or alphaInt
	}

	val isFullyTransparent get() = alpha <= 0f
	val isWhite: Boolean get() = red >= 1f && green >= 1f && blue >= 1f && alpha >= 1f

	fun canRepresentHue(): Boolean {
		val min = red.coerceAtMost(green).coerceAtMost(blue)
		val max = red.coerceAtLeast(green).coerceAtLeast(blue)
		return min != max
	}

	fun hue(ifNoHue: Float = 0f): Float {
		val min = red.coerceAtMost(green).coerceAtMost(blue)
		val max = red.coerceAtLeast(green).coerceAtLeast(blue)

		if (min == max) {
			return ifNoHue
		}

		val diff = max - min

		return if (max == red && green >= blue) {
			60f * (green - blue) / diff
		} else if (max == red) {
			60f * (green - blue) / diff + 360f
		} else if (max == green) {
			60f * (blue - red) / diff + 120f
		} else if (max == blue) {
			60f * (red - green) / diff + 240f
		} else {
			throw IllegalStateException("Whut $red $green $blue ($min / $max)")
		}
	}

	fun toHSV(): HSVColor {
		val min = red.coerceAtMost(green).coerceAtMost(blue)
		val max = red.coerceAtLeast(green).coerceAtLeast(blue)

		if (min == max) {
			return HSVColor(0f, if (max == 0f) 0f else 1f - min / max, max)
		}

		val diff = max - min

		val hue = if (max == red && green >= blue) {
			60f * (green - blue) / diff
		} else if (max == red) {
			60f * (green - blue) / diff + 360f
		} else if (max == green) {
			60f * (blue - red) / diff + 120f
		} else if (max == blue) {
			60f * (red - green) / diff + 240f
		} else {
			throw IllegalStateException("Whut $red $green $blue ($min / $max)")
		}

		return HSVColor(hue, 1f - min / max, max)
	}

	fun toHexStringRGB(): String {
		return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}"
	}

	fun toHexStringRGBA(): String {
		return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}${hex(alphaInt)}"
	}

	fun toHexStringARGB(): String {
		return "#${hex(alphaInt)}${hex(redInt)}${hex(greenInt)}${hex(blueInt)}"
	}

	override fun toString(): String {
		return "RGBAColor[$red $green $blue $alpha]"
	}

	operator fun component1() = red
	operator fun component2() = green
	operator fun component3() = blue
	operator fun component4() = alpha

	fun toIntInv(): Int {
		return (blueInt shl 16) or (greenInt shl 8) or redInt
	}

	fun toRGB(): Int {
		return (redInt shl 16) or (greenInt shl 8) or blueInt
	}

	fun copy(red: Float = this.red, green: Float = this.green, blue: Float = this.blue, alpha: Float = this.alpha): RGBAColor {
		return RGBAColor(red, green, blue, alpha)
	}

	override fun compareTo(other: RGBAColor): Int {
		if (canRepresentHue() && other.canRepresentHue())
			return hue().compareTo(other.hue()).let {
				if (it != 0)
					it
				else
					toHSV().compareTo(other.toHSV())
			}

		return comparator.compare(this, other)
	}

	override fun equals(other: Any?): Boolean {
		return other === this || other is RGBAColor && comparator.compare(this, other) == 0
	}

	override fun hashCode(): Int {
		return red.hashCode() + green.hashCode() * 31 + blue.hashCode() * 31 * 31 + alpha.hashCode() * 31 * 31 * 31
	}

	fun linearInterpolation(t: Float, other: RGBAColor, interpolateAlpha: Boolean = true): RGBAColor {
		return RGBAColor(
			linearInterpolation(t, red, other.red),
			linearInterpolation(t, green, other.green),
			linearInterpolation(t, blue, other.blue),
			if (interpolateAlpha) linearInterpolation(t, alpha, other.alpha) else alpha,
		)
	}

	operator fun times(other: RGBAColor): RGBAColor {
		if (isWhite)
			return other
		else if (other.isWhite)
			return this

		return RGBAColor(red * other.red, green * other.green, blue * other.blue, alpha * other.alpha)
	}

	@Suppress("unused")
	companion object {
		private val comparator = Comparator
			.comparing(RGBAColor::red)
			.thenComparing(RGBAColor::green)
			.thenComparing(RGBAColor::blue)
			.thenComparing(RGBAColor::alpha)

		@JvmField val TRANSPARENT_BLACK = RGBAColor(0f, 0f, 0f, 0f)
		@JvmField val TRANSPARENT_WHITE = RGBAColor(1f, 1f, 1f, 0f)

		@JvmField val BLACK = RGBAColor(0f, 0f, 0f)
		@JvmField val WHITE = RGBAColor(1f, 1f, 1f)
		@JvmField val RED = RGBAColor(1f, 0f, 0f)
		@JvmField val GREEN = RGBAColor(0f, 1f, 0f)
		@JvmField val LIGHT_GREEN = RGBAColor(136, 255, 124)
		@JvmField val SLATE_GRAY = RGBAColor(64, 64, 64)
		@JvmField val GRAY = rgb(0x2C2C2CL)

		@JvmField val DARK_BLUE = rgb(ChatFormatting.DARK_BLUE.color!!)
		@JvmField val DARK_GREEN = rgb(ChatFormatting.DARK_GREEN.color!!)
		@JvmField val DARK_AQUA = rgb(ChatFormatting.DARK_AQUA.color!!)
		@JvmField val DARK_RED = rgb(ChatFormatting.DARK_RED.color!!)
		@JvmField val DARK_PURPLE = rgb(ChatFormatting.DARK_PURPLE.color!!)
		@JvmField val GOLD = rgb(ChatFormatting.GOLD.color!!)
		@JvmField val DARK_GRAY = rgb(ChatFormatting.DARK_GRAY.color!!)
		@JvmField val BLUE = rgb(ChatFormatting.BLUE.color!!)
		@JvmField val AQUA = rgb(ChatFormatting.AQUA.color!!)
		@JvmField val LIGHT_PURPLE = rgb(ChatFormatting.LIGHT_PURPLE.color!!)
		@JvmField val YELLOW = rgb(ChatFormatting.YELLOW.color!!)

		@JvmField val LOW_POWER = RGBAColor(173, 41, 41)
		@JvmField val FULL_POWER = RGBAColor(255, 242, 40)
		@JvmField val LOW_MATTER = RGBAColor(0, 24, 148)
		@JvmField val FULL_MATTER = RGBAColor(72, 90, 255)
		@JvmField val LOW_PATTERNS = RGBAColor(44, 104, 57)
		@JvmField val FULL_PATTERNS = RGBAColor(65, 255, 87)

		@JvmField val HALF_TRANSPARENT = RGBAColor(1f, 1f, 1f, 0.5f)
		@JvmField val REDDISH = RGBAColor(1f, 0.4f, 0.4f)

		@JvmField
		val CODECRGBA: Codec<RGBAColor> = RecordCodecBuilder.create {
			it.group(
				Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red),
				Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green),
				Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue),
				Codec.floatRange(0f, 1f).optionalFieldOf("alpha", 1f).forGetter(RGBAColor::alpha),
			).apply(it, ::RGBAColor)
		}

		@JvmField
		val CODECRGB: Codec<RGBAColor> = RecordCodecBuilder.create {
			it.group(
				Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red),
				Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green),
				Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue),
			).apply(it, ::RGBAColor)
		}

		fun rgb(color: Int): RGBAColor {
			val r = (color and 0xFF0000 ushr 16) / 255f
			val g = (color and 0xFF00 ushr 8) / 255f
			val b = (color and 0xFF) / 255f
			return RGBAColor(r, g, b)
		}

		fun rgb(color: Long): RGBAColor {
			val r = (color and 0xFF0000 ushr 16) / 255f
			val g = (color and 0xFF00 ushr 8) / 255f
			val b = (color and 0xFF) / 255f
			return RGBAColor(r, g, b)
		}

		fun bgr(color: Int): RGBAColor {
			val r = (color and 0xFF0000 ushr 16) / 255f
			val g = (color and 0xFF00 ushr 8) / 255f
			val b = (color and 0xFF) / 255f
			return RGBAColor(b, g, r)
		}

		fun bgr(color: Long): RGBAColor {
			val r = (color and 0xFF0000 ushr 16) / 255f
			val g = (color and 0xFF00 ushr 8) / 255f
			val b = (color and 0xFF) / 255f
			return RGBAColor(b, g, r)
		}

		fun abgr(color: Int): RGBAColor {
			val r = (color and -0x1000000 ushr 24) / 255f
			val g = (color and 0xFF0000 ushr 16) / 255f
			val b = (color and 0xFF00 ushr 8) / 255f
			val a = (color and 0xFF) / 255f
			return RGBAColor(a, b, g, r)
		}

		fun argb(color: Int): RGBAColor {
			val a = (color and -0x1000000 ushr 24) / 255f
			val r = (color and 0xFF0000 ushr 16) / 255f
			val g = (color and 0xFF00 ushr 8) / 255f
			val b = (color and 0xFF) / 255f
			return RGBAColor(r, g, b, a)
		}

		private val hexChars = CharAVLTreeSet()

		init {
			"#0123456789abcdefABCDEF".forEach { hexChars.add(it) }
		}

		fun isHexCharacter(char: Char): Boolean {
			return char in hexChars
		}

		private val shorthandRGBHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE)
		private val longhandRGBHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE)

		private val shorthandRGBAHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE)
		private val longhandRGBAHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE)

		fun fromHexStringRGB(value: String): RGBAColor? {
			if (value.length == 3 || value.length == 4) {
				val match = shorthandRGBHex.find(value) ?: return null
				val red = match.groupValues[1].toIntOrNull(16) ?: return null
				val green = match.groupValues[2].toIntOrNull(16) ?: return null
				val blue = match.groupValues[3].toIntOrNull(16) ?: return null
				return RGBAColor(red * 16, green * 16, blue * 16)
			} else if (value.length == 6 || value.length == 7) {
				val match = longhandRGBHex.find(value) ?: return null
				val red = match.groupValues[1].toIntOrNull(16) ?: return null
				val green = match.groupValues[2].toIntOrNull(16) ?: return null
				val blue = match.groupValues[3].toIntOrNull(16) ?: return null
				return RGBAColor(red, green, blue)
			} else {
				return null
			}
		}

		fun fromHexStringRGBA(value: String): RGBAColor? {
			if (value.length == 4 || value.length == 5) {
				val match = shorthandRGBAHex.find(value) ?: return null
				val red = match.groupValues[1].toIntOrNull(16) ?: return null
				val green = match.groupValues[2].toIntOrNull(16) ?: return null
				val blue = match.groupValues[3].toIntOrNull(16) ?: return null
				val alpha = match.groupValues[4].toIntOrNull(16) ?: return null
				return RGBAColor(red * 16, green * 16, blue * 16, alpha * 16)
			} else if (value.length == 8 || value.length == 9) {
				val match = longhandRGBAHex.find(value) ?: return null
				val red = match.groupValues[1].toIntOrNull(16) ?: return null
				val green = match.groupValues[2].toIntOrNull(16) ?: return null
				val blue = match.groupValues[3].toIntOrNull(16) ?: return null
				val alpha = match.groupValues[4].toIntOrNull(16) ?: return null
				return RGBAColor(red, green, blue, alpha)
			} else {
				return null
			}
		}
	}
}

fun linearInterpolation(t: Float, a: RGBAColor, b: RGBAColor): RGBAColor {
	return a.linearInterpolation(t, b)
}
