package ru.dbotthepony.mc.otm.client

import com.mojang.blaze3d.systems.RenderSystem
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.resources.ResourceLocation
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraftforge.client.event.RenderGuiEvent
import net.minecraftforge.client.event.RenderGuiOverlayEvent
import net.minecraftforge.client.event.ScreenEvent
import net.minecraftforge.client.gui.overlay.ForgeGui
import net.minecraftforge.client.gui.overlay.GuiOverlayManager
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import java.util.*
import kotlin.math.ceil

object MatteryGUI {
	private val BARS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "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)

	private val BARS_HP = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "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 var originalBedButtonX = -1
	private var originalBedButtonY = -1

	private var lastState: MatteryPlayerCapability? = null

	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?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK {
			if (!it.willBecomeAndroid) {
				screen.leaveBedButton.x = originalBedButtonX
				screen.leaveBedButton.y = originalBedButtonY
				originalBedButtonX = -1
				originalBedButtonY = -1
				return@ifPresentK
			}

			val dispersion = (10.0 * Math.max(0, it.ply.sleepTimer - 20) / (MatteryPlayerCapability.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?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK {
				if (it.isAndroid) screen.title = TranslatableComponent("otm.death_reason")
			}
		}
	}

	private val FOOD_LEVEL_ELEMENT by lazy {
		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "food_level"))
	}

	private val AIR_LEVEL_ELEMENT by lazy {
		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "air_level"))
	}

	private val PLAYER_HEALTH_ELEMENT by lazy {
		GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "player_health"))
	}

	var iteration = 0
	var showIterationUntil = 0L
	var showIterationUntilFade = 0L

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

	private fun showIteration(event: RenderGuiEvent.Post) {
		if (minecraft.player?.matteryPlayer?.isAndroid != true) {
			return
		}

		val time = System.currentTimeMillis()

		if (time > showIterationUntilFade) {
			return
		}

		val stack = event.guiGraphics.pose()
		val window = event.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())

		event.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)

		minecraft.font.drawAligned(stack, text, RenderGravity.CENTER_CENTER, x + 1f, y + 1f, RGBAColor.BLACK)
		minecraft.font.drawAligned(stack, text, RenderGravity.CENTER_CENTER, x, y, RGBAColor.WHITE)

		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]
			minecraft.font.drawAligned(stack, component.second, RenderGravity.CENTER_CENTER, x + 1f, y + 1f, RGBAColor.BLACK)
			minecraft.font.drawAligned(stack, component.second, RenderGravity.CENTER_CENTER, x, y, RGBAColor(color, color, color))

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

		stack.popPose()

		popScissorRect()
	}

	fun onRenderGuiEvent(event: RenderGuiEvent.Post) {
		showIteration(event)
	}

	private fun renderFoodAndAir(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) {
		val ply: LocalPlayer = minecraft.player ?: return

		if (ply.vehicle is LivingEntity) {
			return
		}

		var mattery = ply.matteryPlayer

		if (!ply.isAlive && mattery == null) {
			mattery = lastState
		} else if (ply.isAlive && mattery != null) {
			lastState = mattery
		}

		if (mattery != null && mattery.isAndroid) {
			event.isCanceled = true

			if (event.overlay === AIR_LEVEL_ELEMENT) {
				return
			}

			if (!gui.shouldDrawSurvivalElements()) return

			var level: Float

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

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

			gui.setupOverlayRenderState(true, false)
			RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)

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

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

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

			val formattedPower = mattery.androidEnergy.batteryLevel.formatPower()

			event.guiGraphics.drawScaledAligned(gui.font, formattedPower, 0.5f, RenderGravity.CENTER_LEFT, left + 83f, top + 4f, RGBAColor.BLACK.toRGB())
			event.guiGraphics.drawScaledAligned(gui.font, formattedPower, 0.5f, RenderGravity.CENTER_LEFT, left + 82f, top + 3f, RGBAColor.YELLOW.toRGB())
		}
	}

	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): Int {
		if (player.hasEffect(MobEffects.POISON)) {
			return RGBAColor.DARK_GREEN.toRGB()
		} else if (player.hasEffect(MobEffects.WITHER)) {
			return RGBAColor.WHITE.toRGB()
		} else if (player.isFullyFrozen) {
			return RGBAColor.AQUA.toRGB()
		}

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

	private fun renderPlayerHealth(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) {
		if (!ClientConfig.ANDROID_HEALTH_HUD) return

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

		if (!ply.isAlive && mattery == null) {
			mattery = lastState
		} else if (ply.isAlive && mattery != null) {
			lastState = mattery
		}

		if (mattery != null && mattery.isAndroid) {
			event.isCanceled = true

			if (!gui.shouldDrawSurvivalElements()) return

			val level: Float = (ply.health / ply.maxHealth).coerceIn(0.0f .. 1.0f)
			val levelAbsorb: Float = (ply.absorptionAmount / ply.maxHealth).coerceIn(0.0f .. 1.0f)

			gui.setupOverlayRenderState(true, false)
			RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)

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

			HEALTH_BG.render(event.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(event.guiGraphics, left.toFloat(), top.toFloat(), width = ceil(levelArmor * 81f))
			}

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

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

			event.guiGraphics.drawScaledAligned(minecraft.font, formattedHealth, 0.5f, RenderGravity.CENTER_RIGHT, left - 4f, top + 4f, RGBAColor.BLACK.toRGB())
			event.guiGraphics.drawScaledAligned(minecraft.font, formattedHealth, 0.5f, RenderGravity.CENTER_RIGHT, left - 5f, top + 3f, getHealthColorForPlayer(ply))
		}
	}

	fun onLayerRenderEvent(event: RenderGuiOverlayEvent.Pre) {
		val gui = minecraft.gui as? ForgeGui ?: return
		if (minecraft.options.hideGui || !gui.shouldDrawSurvivalElements()) return

		if (event.overlay == FOOD_LEVEL_ELEMENT || event.overlay == AIR_LEVEL_ELEMENT) {
			renderFoodAndAir(event, gui)
		} else if (event.overlay == PLAYER_HEALTH_ELEMENT)  {
			renderPlayerHealth(event, gui)
		}
	}
}
