package org.neoflock.neocomputers.utils

// based off the ImplementedContainer of https://docs.fabricmc.net/develop/blocks/block-containers
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderStateShard.ShaderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.world.Container;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.Resource
import net.minecraft.world.ContainerHelper
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.gui.widget.DynamicSlot

// Common container interface, assumes the entire purpose is purely raw item storage
interface GenericContainer : Container {
    fun getItems(): NonNullList<ItemStack>

    override fun getContainerSize(): Int {
        return getItems().size
    }

    override fun isEmpty(): Boolean {
        return getItems().all { it.isEmpty }
    }

    override fun getItem(i: Int): ItemStack {
        return getItems()[i]
    }

    override fun removeItem(slot: Int, count: Int): ItemStack {
        val res = ContainerHelper.removeItem(getItems(), slot, count)
        if (!res.isEmpty) setChanged()
        return res
    }

    override fun setItem(slot: Int, itemStack: ItemStack) {
        getItems()[slot] = itemStack

        // in case of bullshit
        if(itemStack.count > itemStack.maxStackSize) {
            // rip items
            itemStack.count = itemStack.maxStackSize
        }
    }

    override fun removeItemNoUpdate(i: Int): ItemStack {
        return ContainerHelper.takeItem(getItems(), i)
    }

    override fun clearContent() {
        getItems().clear()
    }
}

abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var container: Container): AbstractContainerMenu(menuType, id) {
    fun addInventorySlots(inventory: Inventory, x: Int, y: Int) {
        // Based off the code in ChestMenu
        for (i in 0..2) {
            for (j in 0..8) {
                this.addSlot(Slot(inventory, j + i * 9 + 9, 8 + j * 18, y + i * 18))
            }
        }

        addInventoryHotbar(inventory, x, y + 3 * 18 + 4)
    }

    fun addInventoryHotbar(inventory: Inventory, x: Int, y: Int) {
        for (i in 0..8) {
            this.addSlot(Slot(inventory, i, x + i * 18, y))
        }
    }

    // taken from https://docs.fabricmc.net/develop/blocks/container-menus
    override fun quickMoveStack(player: Player, i: Int): ItemStack? {
        val slot = slots[i]

        if(!slot.hasItem()) return ItemStack.EMPTY

        val stack = slot.item
        val copied = stack.copy()
        val contSize = container.containerSize

        if(i < contSize) {
            if(!this.moveItemStackTo(stack, contSize, slots.size, true)) {
                return ItemStack.EMPTY
            }
        } else if(!this.moveItemStackTo(stack, 0, contSize, false)) {
            return ItemStack.EMPTY
        }

        if(stack.isEmpty) {
            slot.setByPlayer(ItemStack.EMPTY)
        } else {
            slot.setChanged()
        }

        return copied
    }

    override fun stillValid(player: Player): Boolean {
        return container.stillValid(player)
    }
}

abstract class GenericContainerScreen<T: GenericContainerMenu>(menu: T, inventory: Inventory, component: Component): AbstractContainerScreen<T>(menu, inventory, component) {
    open fun shouldCenterTitle() = true
    open fun shouldRenderTooltip() = true
    open fun findMenuTexture(): ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/background.png")

    open fun getBoundBlockEntityType(): Set<BlockEntityType<*>> = setOf()

    open fun processScreenStatePacket(buf: FriendlyByteBuf) {}

    val imageX: Int
        get() = (width - imageWidth) / 2

    val imageY: Int
        get() = (height - imageHeight) / 2

    var widgets = mutableListOf<AbstractWidget>()

    val RENDER_TYPE = { r: ResourceLocation ->
        RenderType.create("nc_gui_bg", DefaultVertexFormat.POSITION_TEX, VertexFormat.Mode.QUADS, RenderType.TRANSIENT_BUFFER_SIZE, RenderType.CompositeState.builder()
            .setShaderState(ShaderStateShard.POSITION_TEX_SHADER)
            .setTextureState(RenderStateShard.TextureStateShard(r, false, false))
            .createCompositeState(false))
    }

    override fun init() {
        super.init()

        if(shouldCenterTitle()) this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2
    }

    override fun renderBg(guiGraphics: GuiGraphics, f: Float, i: Int, j: Int) {
        val menuTex = findMenuTexture()
        val cx = (width - imageWidth) / 2
        val cy = (height - imageHeight) / 2
        guiGraphics.pose().pushPose()
        guiGraphics.pose().translate(cx.toFloat(), cy.toFloat(), 0f)

        guiGraphics.blit(menuTex, 0, 0, 0, 0, imageWidth, imageHeight)
        renderbg(guiGraphics, f, i-cx, j-cy)

        for (widget in widgets) {
            widget.render(guiGraphics, i-cx, j-cy, f)
        }

        for (slot in menu.slots) {
            if (slot is DynamicSlot) {
                slot.draw(guiGraphics, i-cx, j-cy)
            }
        }

        guiGraphics.pose().popPose()
    }

    open fun renderbg(guiGraphics: GuiGraphics, partialTick: Float, mouseX: Int, mouseY: Int) {}

    open fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) { }

    override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
        super.render(graphics, mouseX, mouseY, something)

        graphics.pose().pushPose()
        graphics.pose().translate(imageX.toFloat(), imageY.toFloat(), 0f)
        renderCustomOverlay(graphics, mouseX-imageX, mouseY-imageY, something)
        graphics.pose().popPose() // not even doing this because it's better anymore, im just doing this because i dont want to change it back

        if(shouldRenderTooltip()) super.renderTooltip(graphics, mouseX, mouseY)
    }

    override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
        super.mouseClicked(mouseX, mouseY, button)
        for (widget in widgets) {
            if (widget.mouseClicked(mouseX-imageX, mouseY-imageY, button)) return true
        }

        return false
    }

    fun addWidget(widget: AbstractWidget) {
        widgets.add(widget)
    }
}