package ru.dbotthepony.mc.otm.client

import com.mojang.blaze3d.platform.InputConstants
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.client.gui.screens.inventory.InventoryScreen
import net.minecraft.world.inventory.InventoryMenu
import net.minecraft.world.inventory.Slot
import net.minecraftforge.client.event.MovementInputUpdateEvent
import net.minecraftforge.client.event.ScreenEvent
import net.minecraftforge.client.event.ScreenEvent.MouseDragged
import net.minecraftforge.client.event.ScreenEvent.MouseScrolled
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.android.feature.JumpBoostFeature
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.ExoPackInventoryScreen
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.Panel2Widget
import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket
import ru.dbotthepony.mc.otm.compat.cos.isCosmeticArmorScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.integerDivisionUp
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import java.util.WeakHashMap

fun onMovementInputUpdate(event: MovementInputUpdateEvent) {
	val ply = event.entity
	val input = event.input

	if (ply.isCreative || ply.isSpectator)
		return

	val cap = ply.matteryPlayer ?: return

	if (cap.isAndroid) {
		(cap.getFeature(AndroidFeatures.JUMP_BOOST) as JumpBoostFeature?)?.movementTick(input.jumping, input.shiftKeyDown)
	}

	if (!cap.isAndroid || cap.hasFeature(AndroidFeatures.AIR_BAGS))
		return

	if (ply.abilities.mayfly) {
		cap.lastJumpTicks = 14
	} else {
		if (ply.isInWater) {
			if (ply.onGround()) {
				cap.lastJumpTicks = 14
			}

			if (ply.isSwimming) {
				ply.isSwimming = false
			}

			if (cap.lastJumpTicks <= 0) {
				input.jumping = false
				input.up = false
			} else {
				cap.lastJumpTicks--
			}
		}
	}
}

var shouldOpenVanillaInventory = false
var inventoryScroll = 0

private fun inventoryLogic(event: ScreenEvent.Init.Post) {
	val player = minecraft.player?.matteryPlayer ?: return

	if (!player.hasExoPack) {
		return
	}

	val eventScreen = event.screen
	val screen = if (eventScreen is AbstractContainerScreen<*> && (eventScreen.menu is InventoryMenu || eventScreen.isCosmeticArmorScreen)) eventScreen else return

	val widget = Panel2Widget(LargeRectangleButtonPanel(screen, null,
		x = screen.guiLeft + screen.xSize - 2f - LargeRectangleButtonPanel.SIZE,
		y = screen.guiTop.toFloat() - LargeRectangleButtonPanel.SIZE - 2,
		skinElement = Widgets18.RETURN_ARROW_LEFT,
		skinElementWinding = UVWindingOrder.FLOP,
		onPress = {
			shouldOpenVanillaInventory = false
			val mouseX = minecraft.mouseHandler.xpos()
			val mouseY = minecraft.mouseHandler.ypos()

			event.screen.onClose()
			minecraft.setScreen(ExoPackInventoryScreen(player.exoPackMenu))

			InputConstants.grabOrReleaseMouse(minecraft.window.window, 212993, mouseX, mouseY)
		}).also { it.tooltips.add(TranslatableComponent("otm.gui.exopack.go_in")) })

	event.addListener(widget)

	tickWhileClient({ minecraft.screen == screen }) {
		widget.panel.x = screen.guiLeft + screen.xSize - 2f - LargeRectangleButtonPanel.SIZE
		widget.panel.y = screen.guiTop.toFloat() - LargeRectangleButtonPanel.SIZE - 2f
	}
}

private class InventoryScrollbarPanel<S : Screen>(screen: S, matteryPlayer: MatteryPlayerCapability) : DiscreteScrollBarPanel<S>(
	screen, null, { integerDivisionUp(matteryPlayer.exoPackContainer.containerSize, 9) }, { _, _, newScroll ->
		inventoryScroll = newScroll
		MenuNetworkChannel.sendToServer(InventoryScrollPacket(newScroll).also { it.play(matteryPlayer.ply) })
	}, isSlim = true
)

private data class ScreenScrollbarConfig(val x: Float, val y: Float, val height: Float)
private val foundScreenConfigurations = WeakHashMap<AbstractContainerScreen<*>, ScreenScrollbarConfig>()

private fun exosuitInventoryLogic(screen: Screen, addListener: (GuiEventListener) -> Unit) {
	val player = minecraft.player ?: return
	val matteryPlayer = player.matteryPlayer ?: return

	if (!matteryPlayer.hasExoPack || matteryPlayer.exoPackContainer.containerSize == 0) {
		return
	}

	if (screen !is AbstractContainerScreen<*> || screen.menu is MatteryMenu || screen.menu is InventoryMenu) {
		return
	}

	val existingConfig = foundScreenConfigurations[screen]
	val foundInventorySlots = Int2ObjectArrayMap<Slot>()

	for (slot in screen.menu.slots) {
		if (slot.container === player.inventory && slot.slotIndex in 9 .. 35) {
			foundInventorySlots[slot.slotIndex] = slot
		}
	}

	var passed = true

	for (i in 9 .. 35) {
		if (i !in foundInventorySlots) {
			if (existingConfig == null) {
				return
			} else {
				passed = false
				break
			}
		}
	}

	val config: ScreenScrollbarConfig

	if (passed) {
		val mostTop = foundInventorySlots.values
			.stream()
			.min { o1, o2 -> o1.y.compareTo(o2.y) }
			.get().y.toFloat()

		val mostBottom = foundInventorySlots.values
			.stream()
			.max { o1, o2 -> o1.y.compareTo(o2.y) }
			.get().y.toFloat()

		val mostLeft = foundInventorySlots.values
			.stream()
			.max { o1, o2 -> o1.x.compareTo(o2.x) }
			.get().x.toFloat() + 26f

		config = ScreenScrollbarConfig(mostLeft + screen.guiLeft - 9f, mostTop + screen.guiTop - 1f, mostBottom - mostTop + AbstractSlotPanel.SIZE)
		foundScreenConfigurations[screen] = config
	} else if (existingConfig != null) {
		config = existingConfig
	} else {
		return
	}

	val findScrollbar = (screen.renderables.firstOrNull { it is Panel2Widget<*, *> && it.panel is InventoryScrollbarPanel } as Panel2Widget<*, *>?)?.panel as InventoryScrollbarPanel?
	val scrollbar = findScrollbar ?: InventoryScrollbarPanel(screen, matteryPlayer)

	scrollbar.x = config.x
	scrollbar.y = config.y
	scrollbar.height = config.height

	if (findScrollbar == null) {
		val widget = Panel2Widget(scrollbar)
		addListener(widget)
	}

	scrollbar.scroll = inventoryScroll
}

fun onMouseDragged(event: MouseDragged.Pre) {
	val screen = minecraft.screen as? AbstractContainerScreen<*> ?: return

	if (screen is MatteryScreen<*>)
		return

	for (widget in screen.renderables) {
		if (widget is Panel2Widget<*, *>) {
			if (widget.panel.mouseDraggedChecked(event.mouseX, event.mouseY, event.mouseButton, event.dragX, event.dragY)) {
				event.isCanceled = true
				return
			}
		}
	}
}

fun onMouseScrolled(event: MouseScrolled.Pre) {
	val screen = minecraft.screen as? AbstractContainerScreen<*> ?: return

	if (screen is MatteryScreen<*>)
		return

	for (widget in screen.renderables) {
		if (widget is Panel2Widget<*, *>) {
			if (widget.panel.mouseScrolledChecked(event.mouseX, event.mouseY, event.scrollDelta)) {
				event.isCanceled = true
				return
			}

			if (ClientConfig.EXOPACK_FREE_SCROLL && widget.panel is InventoryScrollbarPanel) {
				val slot = screen.slotUnderMouse

				if (slot != null && (slot.container == minecraft.player?.inventory && slot.containerSlot in 9 .. 35 || slot.container == minecraft.player?.matteryPlayer?.exoPackContainer)) {
					widget.panel.mouseScrolledInner(event.mouseX, event.mouseY, event.scrollDelta)
					event.isCanceled = true
					return
				}
			}

			return
		}
	}
}

fun onPostScreenInit(event: ScreenEvent.Init.Post) {
	inventoryLogic(event)
	exosuitInventoryLogic(event.screen, event::addListener)
}

fun onScreenOpen(event: ScreenEvent.Opening) {
	if (shouldOpenVanillaInventory || minecraft.player?.isCreative == true) {
		return
	}

	val player = minecraft.player?.matteryPlayer ?: return

	if (player.hasExoPack && event.newScreen is InventoryScreen) {
		event.newScreen = ExoPackInventoryScreen(player.exoPackMenu)
	}
}
