package ru.dbotthepony.mc.otm.client

import com.google.common.collect.ImmutableList
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.ChatFormatting
import net.minecraft.client.DeltaTracker
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.LayeredDraw
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.DeathScreen
import net.minecraft.client.gui.screens.InBedChatScreen
import net.minecraft.client.player.LocalPlayer
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.Style
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.ShieldItem
import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent
import net.neoforged.neoforge.client.event.ScreenEvent
import net.neoforged.neoforge.client.gui.VanillaGuiLayers
import net.neoforged.neoforge.common.ItemAbilities
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.capability.MatteryPlayer
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas
import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.OverdriveThatMatters.loc
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.random
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.registry.game.AndroidFeatures
import java.util.*
import kotlin.math.PI
import kotlin.math.ceil

object MatteryGUI {
	private var originalBedButtonX = -1
	private var originalBedButtonY = -1

	private val buttonShaker = Random()

	fun onScreenRender(event: ScreenEvent.Render.Pre) {
		val screen = minecraft.screen as? InBedChatScreen

		if (screen == null || (screen.leaveBedButton as Button?) == null) {
			originalBedButtonX = -1
			originalBedButtonY = -1
			return
		}

		if (originalBedButtonX == -1 && originalBedButtonY == -1) {
			originalBedButtonX = screen.leaveBedButton.x
			originalBedButtonY = screen.leaveBedButton.y
		}

		minecraft.player?.matteryPlayer?.let {
			if (!it.willBecomeAndroid) {
				screen.leaveBedButton.x = originalBedButtonX
				screen.leaveBedButton.y = originalBedButtonY
				originalBedButtonX = -1
				originalBedButtonY = -1
				return@let
			}

			val dispersion = (10.0 * Math.max(0, it.ply.sleepTimer - 20) / (MatteryPlayer.SLEEP_TICKS_LIMIT - 20)).toInt()

			screen.leaveBedButton.x =
				originalBedButtonX - dispersion / 2 + (buttonShaker.nextDouble() * dispersion).toInt()
			screen.leaveBedButton.y =
				originalBedButtonY - dispersion / 2 + (buttonShaker.nextDouble() * dispersion).toInt()
		}
	}

	fun onOpenGUIEvent(event: ScreenEvent.Opening) {
		originalBedButtonX = -1
		originalBedButtonY = -1

		if (minecraft.player == null) return

		val screen = event.screen

		if (screen is DeathScreen) {
			minecraft.player?.matteryPlayer?.let {
				if (it.isAndroid) screen.title = TranslatableComponent("otm.death_reason")
			}
		}
	}

	fun onLayerRenderEvent(event: RenderGuiLayerEvent.Pre) {
		if (minecraft.player?.matteryPlayer?.isAndroid == true) {
			if (event.name == VanillaGuiLayers.FOOD_LEVEL
				|| event.name == VanillaGuiLayers.AIR_LEVEL
				|| (event.name == VanillaGuiLayers.PLAYER_HEALTH && ClientConfig.HUD.ANDROID_HEALTH_BAR)) {
				event.isCanceled = true
			}
		}
	}

	fun registerGuiLayers(event: RegisterGuiLayersEvent) {
		event.registerBelow(VanillaGuiLayers.FOOD_LEVEL, loc("android_energy_bar"), AndroidEnergyBarLayer())
		event.registerBelow(VanillaGuiLayers.PLAYER_HEALTH, loc("android_health_bar"), AndroidHealthBarLayer())

		event.registerAboveAll(loc("android_iteration"), AndroidIterationLayer())
		event.registerAbove(VanillaGuiLayers.CAMERA_OVERLAYS, loc("android_low_power"), AndroidLowPowerLayer())
	}

	val SMALL_FONT = loc("small")
	val SMALL_FONT_STYLE: Style = Style.EMPTY.withFont(SMALL_FONT)
	val STANDARD_FONT_STYLE: Style = Style.EMPTY

	class AndroidEnergyBarLayer : LayeredDraw.Layer {
		override fun render(
			graphics: GuiGraphics,
			delta: DeltaTracker
		) {
			if (minecraft.options.hideGui || !minecraft.gameMode!!.canHurtPlayer()) return

			val ply: LocalPlayer = minecraft.player ?: return

			if (ply.vehicle is LivingEntity)
				return

			val gui = minecraft.gui
			val mattery = ply.matteryPlayer

			if (mattery.isAndroid) {
				var level: Float

				if (mattery.androidEnergy.maxBatteryLevel.isZero) {
					level = 0f
				} else {
					level = mattery.androidEnergy.batteryLevel.percentage(mattery.androidEnergy.maxBatteryLevel)

					if (level >= 0.98f)
						level = 1f
				}

				RenderSystem.enableBlend()
				RenderSystem.defaultBlendFunc()
				RenderSystem.disableDepthTest()
				RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)

				val width = minecraft.window.guiScaledWidth
				val height = minecraft.window.guiScaledHeight
				val left = width / 2 + 10
				val top: Int = height - gui.rightHeight
				gui.rightHeight += 10

				val leftPadding = ceil(level * 80f - 0.5f)

				val guiGraphics = MGUIGraphics(graphics)

				if (ply.hasEffect(MobEffects.HUNGER)) {
					CHARGE_HUNGER_BG.render(guiGraphics, left.toFloat(), top.toFloat())
					CHARGE_HUNGER.renderPartial(guiGraphics, left.toFloat() - leftPadding + 81f, top.toFloat(), width = leftPadding, leftRight = false)
				} else {
					CHARGE_BG.render(guiGraphics, left.toFloat(), top.toFloat())
					CHARGE.renderPartial(guiGraphics, left.toFloat() - leftPadding + 81f, top.toFloat(), width = leftPadding, leftRight = false)
				}

				val formattedPower = mattery.androidEnergy.batteryLevel.formatPower()
				val scale = if (ClientConfig.HUD.USE_SMALL_FONT) 1f else ClientConfig.HUD.BAR_TEXT_SCALE.toFloat()
				guiGraphics.draw(formattedPower.withStyle(if (ClientConfig.HUD.USE_SMALL_FONT) SMALL_FONT_STYLE else STANDARD_FONT_STYLE), left + CHARGE_BG.width + 2f + scale, top + CHARGE_BG.height / 2f + scale, font = gui.font, scale = scale, gravity = RenderGravity.CENTER_LEFT, color = RGBAColor.FULL_POWER, drawOutline = true)

				RenderSystem.disableBlend()
				RenderSystem.enableDepthTest()
			}
		}

		companion object {
			private val BARS = MatteryAtlas(loc("textures/gui/player_bars.png"), 81f, 36f)
			val CHARGE = BARS.sprite(height = 9f)
			val CHARGE_BG = BARS.sprite(y = 9f, height = 9f)

			val CHARGE_HUNGER = BARS.sprite(y = 18f, height = 9f)
			val CHARGE_HUNGER_BG = BARS.sprite(y = 27f, height = 9f)
		}
	}

	class AndroidHealthBarLayer : LayeredDraw.Layer {
		override fun render(
			graphics: GuiGraphics,
			delta: DeltaTracker
		) {
			if (!ClientConfig.HUD.ANDROID_HEALTH_BAR) return
			if (minecraft.options.hideGui || !minecraft.gameMode!!.canHurtPlayer()) return

			val gui = minecraft.gui
			val ply: LocalPlayer = minecraft.player ?: return
			val mattery = ply.matteryPlayer

			if (mattery.isAndroid && !ply.isCreative) {
				val level: Float = (ply.health / ply.maxHealth).coerceIn(0.0f, 1.0f)
				val levelAbsorb: Float = (ply.absorptionAmount / ply.maxHealth).coerceIn(0.0f, 1.0f)

				RenderSystem.enableBlend()
				RenderSystem.defaultBlendFunc()
				RenderSystem.disableDepthTest()
				RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)

				val width = minecraft.window.guiScaledWidth
				val height = minecraft.window.guiScaledHeight
				val left = width / 2 - 10 - 81
				val top: Int = height - gui.leftHeight
				gui.leftHeight += 10

				val guiGraphics = MGUIGraphics(graphics)

				HEALTH_BG.render(guiGraphics, left.toFloat(), top.toFloat())

				if (mattery.hasFeature(AndroidFeatures.NANOBOTS_ARMOR)) {
					val featArmor = mattery.getFeature(AndroidFeatures.NANOBOTS_ARMOR) as NanobotsArmorFeature
					val levelArmor: Float = (featArmor.layers.toFloat() / (featArmor.strength + 1).toFloat()).coerceIn(0.0f, 1.0f)

					HEALTH_BG_NANOBOTS.renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(levelArmor * 81f))
				}

				getSpriteForPlayer(ply).renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(level * 80f - 0.5f))
				if (levelAbsorb > 0) {
					HEALTH_ABSORB.renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(levelAbsorb * 80f - 0.5f))
				}

				var formattedHealth = TextComponent("%d/%d".format(ply.health.toInt(), ply.maxHealth.toInt()))

				if (ply.absorptionAmount > 0)
					formattedHealth = TextComponent("%d+%d/%d".format(ply.health.toInt(), ply.absorptionAmount.toInt(), ply.maxHealth.toInt()))

				val scale = if (ClientConfig.HUD.USE_SMALL_FONT) 1f else ClientConfig.HUD.BAR_TEXT_SCALE.toFloat()
				guiGraphics.draw(formattedHealth.withStyle(if (ClientConfig.HUD.USE_SMALL_FONT) SMALL_FONT_STYLE else STANDARD_FONT_STYLE), left - 2f, top + HEALTH_BG.height / 2f + 1f * scale, scale = scale, gravity = RenderGravity.CENTER_RIGHT, color = getHealthColorForPlayer(ply), drawOutline = true)

				RenderSystem.disableBlend()
				RenderSystem.enableDepthTest()
			}
		}

		companion object {
			private val BARS_HP = MatteryAtlas(loc("textures/gui/player_bars_health.png"), 81f, 63f)

			val HEALTH = BARS_HP.sprite(height = 9f)
			val HEALTH_BG = BARS_HP.sprite(y = 9f, height = 9f)
			val HEALTH_BG_NANOBOTS = BARS_HP.sprite(y = 18f, height = 9f)

			val HEALTH_POISON = BARS_HP.sprite(y = 27f, height = 9f)
			val HEALTH_WITHER = BARS_HP.sprite(y = 36f, height = 9f)
			val HEALTH_ABSORB = BARS_HP.sprite(y = 45f, height = 9f)
			val HEALTH_FROZEN = BARS_HP.sprite(y = 54f, height = 9f)

			private fun getSpriteForPlayer(player: Player): MatterySprite {
				if (player.hasEffect(MobEffects.POISON)) {
					return HEALTH_POISON
				} else if (player.hasEffect(MobEffects.WITHER)) {
					return HEALTH_WITHER
				} else if (player.isFullyFrozen) {
					return HEALTH_FROZEN
				}

				return HEALTH
			}

			private fun getHealthColorForPlayer(player: Player): RGBAColor {
				if (player.hasEffect(MobEffects.POISON)) {
					return RGBAColor.DARK_GREEN
				} else if (player.hasEffect(MobEffects.WITHER)) {
					return RGBAColor.WHITE
				} else if (player.isFullyFrozen) {
					return RGBAColor.AQUA
				}

				return RGBAColor.RED
			} // можно вынести в конфиг, но для этого нужен селектор цвета

		}
	}

	class AndroidIterationLayer : LayeredDraw.Layer {
		override fun render(
			graphics: GuiGraphics,
			delta: DeltaTracker
		) {
			if (minecraft.options.hideGui) return

			val player = minecraft.player ?: return

			if (!player.matteryPlayer.isAndroid) return

			val time = System.currentTimeMillis()

			if (time > showIterationUntilFade) {
				return
			}

			val guiGraphics = MGUIGraphics(graphics)
			val stack = guiGraphics.pose
			val window = minecraft.window

			stack.pushPose()

			val negateScale = 1f / window.guiScale.toFloat()
			var modifyScale = 6f * negateScale
			val finalScale = modifyScale * window.guiScale.toFloat()

			stack.scale(modifyScale, modifyScale, modifyScale)

			var width = window.guiScaledWidth / modifyScale
			var height = window.guiScaledHeight / modifyScale

			var x = width / 2f
			var y = height / 2f

			val progress = if (time < showIterationUntil) 1f else 1f - (time - showIterationUntil).toFloat() / (showIterationUntilFade - showIterationUntil).toFloat()
			val scissorBase = (y - 12f) * finalScale
			val scissorHeight = (12f + (deathLog.size - 2f).coerceAtLeast(0f) * minecraft.font.lineHeight * modifyScale * 0.0875f) * finalScale

			pushScissorRect(0, (scissorBase + scissorHeight * (1f - progress)).toInt(), window.width, (scissorHeight * progress * 2f).toInt())

			guiGraphics.renderRect(
				0f,
				y - 12f,
				window.guiScaledWidth.toFloat(),
				24f + (deathLog.size - 2f).coerceAtLeast(0f) * minecraft.font.lineHeight * modifyScale * 0.175f,
				color = RGBAColor(1f, 1f, 1f, 0.4f))

			val text = TranslatableComponent("otm.iteration", iteration)

			guiGraphics.draw(text, x + 1f, y + 1f, color = RGBAColor.BLACK, gravity = RenderGravity.CENTER_CENTER)
			guiGraphics.draw(text, x, y, color = RGBAColor.WHITE, gravity = RenderGravity.CENTER_CENTER)

			stack.scale(0.35f, 0.35f, 0.35f)

			modifyScale *= 0.35f

			width = window.guiScaledWidth / modifyScale
			height = window.guiScaledHeight / modifyScale

			x = width / 2f
			y = height / 2f + minecraft.font.lineHeight * finalScale * 0.35f

			var color = 0xFF

			for (i in deathLog.indices.reversed()) {
				val component = deathLog[i]
				guiGraphics.draw(component.second, x + 1f, y + 1f, color = RGBAColor.BLACK, gravity = RenderGravity.CENTER_CENTER)
				guiGraphics.draw(component.second, x, y, color = RGBAColor(color, color, color), gravity = RenderGravity.CENTER_CENTER)

				y += minecraft.font.lineHeight
				color = (color - 0x20).coerceAtLeast(0x0)
			}

			stack.popPose()

			popScissorRect()
		}

		companion object {
			var iteration = 0
			var showIterationUntil = 0L
			var showIterationUntilFade = 0L

			val deathLog = ArrayList<Pair<Int, Component>>()
		}
	}

	class AndroidLowPowerLayer : LayeredDraw.Layer {
		private var errorCode = errorCodes[1]
		private var errorAddress = "0".repeat(16)

		private var errorRegister = "VXE VMMXDE"

		private val errorServerNode by lazy {
			(minecraft.player!!.uuid.leastSignificantBits and 0xFFFFFF).toString(16).uppercase()
		}

		private var displayPowerError = false
		private var lastDisplayPowerErrorUpdate = System.nanoTime()
		private var targetErrorAlpha = System.nanoTime()

		override fun render(
			graphics: GuiGraphics,
			delta: DeltaTracker
		) {
			if (minecraft.options.hideGui || !minecraft.gameMode!!.canHurtPlayer()) return

			val player = minecraft.player ?: return

			if (!player.matteryPlayer.isAndroid) {
				return
			}

			// yes, this is a reference to movie Transcendence (2014)
			if (player.matteryPlayer.androidEnergy.batteryLevel > Decimal.TEN)
				return

			if (System.nanoTime() - lastDisplayPowerErrorUpdate > 100_000_000L) {
				lastDisplayPowerErrorUpdate = System.nanoTime()
				val newStatus = player.random.nextBoolean()

				if (newStatus != displayPowerError && newStatus) {
					targetErrorAlpha = System.nanoTime()
					errorCode = errorCodes[player.random.nextInt(errorCodes.size)]
					errorRegister = "V${registerParts.random(player.random)}${registerParts.random(player.random)} V${registerParts.random(player.random)}${registerParts.random(player.random)}${registerParts.random(player.random)}${registerParts.random(player.random)}${registerParts.random(player.random)}"
					errorAddress = java.lang.Long.toUnsignedString(player.random.nextLong(), 16).uppercase()

					if (errorAddress.length < 16) {
						errorAddress = "0".repeat(16 - errorAddress.length) + errorAddress
					}
				}

				displayPowerError = newStatus
			}

			if (!displayPowerError) return

			val alpha: Float
			val diff = System.nanoTime() - targetErrorAlpha

			if (diff > 50_000_000L) {
				alpha = 1f
			} else {
				alpha = diff / 50_000_000f
			}

			if (alpha != 1f) RenderSystem.setShaderColor(1f, 1f, 1f, alpha)
			val window = minecraft.window

			val x = window.guiScaledWidth / 2f
			var y = window.guiScaledHeight / 2.5f

			minecraft.font.draw(
				graphics.pose(),
				powerAlert,
				x, y,
				scale = 4f,
				gravity = RenderGravity.CENTER_CENTER,
				drawShadow = true
			)

			y += 20f + minecraft.font.lineHeight * 4f

			minecraft.font.draw(
				graphics.pose(),
				powerAlert2,
				x, y,
				gravity = RenderGravity.CENTER_CENTER,
				drawShadow = true
			)

			y += minecraft.font.lineHeight + 2f

			minecraft.font.draw(
				graphics.pose(),
				powerAlert3,
				x, y,
				gravity = RenderGravity.CENTER_CENTER,
				drawShadow = true
			)

			y += minecraft.font.lineHeight * 2f + 4f

			minecraft.font.draw(
				graphics.pose(),
				TranslatableComponent(
					"otm.pwr_alert5",
					errorCode,
					errorServerNode,
					errorAddress,
					errorRegister
				).withStyle(ChatFormatting.YELLOW),
				x, y,
				gravity = RenderGravity.CENTER_CENTER,
				drawShadow = true,
			)

			if (alpha != 1f) RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
		}

		companion object {
			private val powerAlert = TranslatableComponent(
				"otm.pwr_alert1",
				TranslatableComponent("otm.pwr_alert2").withStyle(ChatFormatting.RED)
			).withStyle(ChatFormatting.YELLOW)

			private val powerAlert2 = TranslatableComponent("otm.pwr_alert3").withStyle(ChatFormatting.YELLOW)
			private val powerAlert3 = TranslatableComponent("otm.pwr_alert4").withStyle(ChatFormatting.YELLOW)

			private val errorCodes = ImmutableList.of(
				"005", "06E", "004", "0F1", "7FF", "F0A"
			)

			private val registerParts = ImmutableList.of("A", "B", "C", "D", "E", "F", "M", "D", "X", "Y", "Z")
		}
	}

	fun renderShieldCooldownOverlay(graphics: GuiGraphics, font: Font, stack: ItemStack, x: Int, y: Int): Boolean {
		if (!stack.isEmpty && stack.item is ShieldItem) {
			if (!stack.canPerformAction(ItemAbilities.SHIELD_BLOCK)) return false

			val ply = minecraft.player ?: return false
			if (!ply.isUsingItem || stack != ply.useItem || ply.isBlocking) return false

			val percent = (((stack.item as ShieldItem).getUseDuration(stack, ply) - ply.useItemRemainingTicks + minecraft.gameRenderer.mainCamera.partialTickTime) / 5f).coerceIn(0f, 1f)
			RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f)
			drawArc(graphics.pose(), x + 8f, y + 8f, 8f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * percent, alignAtCenter = true)
			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)

			return true
		}

		return false
	}
}
