package com.mojang.blaze3d.platform;

import net.minecraft.client.main.SilentInitException;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.glfw.Callbacks;
import org.lwjgl.glfw.GLFWErrorCallbackI;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.io.IOException;
import org.lwjgl.stb.STBImage;
import org.lwjgl.glfw.GLFWImage;
import java.io.FileNotFoundException;
import java.io.InputStream;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.MemoryStack;
import java.util.function.BiConsumer;
import org.lwjgl.opengl.GL;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.systems.RenderSystem;
import javax.annotation.Nullable;
import java.util.Optional;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.apache.logging.log4j.Logger;

public final class Window implements AutoCloseable {
    private static final Logger LOGGER;
    private final GLFWErrorCallback defaultErrorCallback;
    private final WindowEventHandler eventHandler;
    private final ScreenManager screenManager;
    private final long window;
    private int windowedX;
    private int windowedY;
    private int windowedWidth;
    private int windowedHeight;
    private Optional<VideoMode> preferredFullscreenVideoMode;
    private boolean fullscreen;
    private boolean actuallyFullscreen;
    private int x;
    private int y;
    private int width;
    private int height;
    private int framebufferWidth;
    private int framebufferHeight;
    private int guiScaledWidth;
    private int guiScaledHeight;
    private double guiScale;
    private String errorSection;
    private boolean dirty;
    private int framerateLimit;
    private boolean vsync;
    
    public Window(final WindowEventHandler dgz, final ScreenManager dgu, final DisplayData dgi, @Nullable final String string4, final String string5) {
        this.defaultErrorCallback = GLFWErrorCallback.create(this::defaultErrorCallback);
        this.errorSection = "";
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        this.screenManager = dgu;
        this.setBootErrorCallback();
        this.setErrorSection("Pre startup");
        this.eventHandler = dgz;
        final Optional<VideoMode> optional7 = VideoMode.read(string4);
        if (optional7.isPresent()) {
            this.preferredFullscreenVideoMode = optional7;
        }
        else if (dgi.fullscreenWidth.isPresent() && dgi.fullscreenHeight.isPresent()) {
            this.preferredFullscreenVideoMode = Optional.<VideoMode>of(new VideoMode(dgi.fullscreenWidth.getAsInt(), dgi.fullscreenHeight.getAsInt(), 8, 8, 8, 60));
        }
        else {
            this.preferredFullscreenVideoMode = Optional.<VideoMode>empty();
        }
        final boolean isFullscreen = dgi.isFullscreen;
        this.fullscreen = isFullscreen;
        this.actuallyFullscreen = isFullscreen;
        final Monitor dgq8 = dgu.getMonitor(GLFW.glfwGetPrimaryMonitor());
        final int n = (dgi.width > 0) ? dgi.width : 1;
        this.width = n;
        this.windowedWidth = n;
        final int n2 = (dgi.height > 0) ? dgi.height : 1;
        this.height = n2;
        this.windowedHeight = n2;
        GLFW.glfwDefaultWindowHints();
        GLFW.glfwWindowHint(139265, 196609);
        GLFW.glfwWindowHint(139275, 221185);
        GLFW.glfwWindowHint(139266, 2);
        GLFW.glfwWindowHint(139267, 0);
        GLFW.glfwWindowHint(139272, 0);
        this.window = GLFW.glfwCreateWindow(this.width, this.height, (CharSequence)string5, (this.fullscreen && dgq8 != null) ? dgq8.getMonitor() : 0L, 0L);
        if (dgq8 != null) {
            final VideoMode dgx9 = dgq8.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.<VideoMode>empty());
            final int n3 = dgq8.getX() + dgx9.getWidth() / 2 - this.width / 2;
            this.x = n3;
            this.windowedX = n3;
            final int n4 = dgq8.getY() + dgx9.getHeight() / 2 - this.height / 2;
            this.y = n4;
            this.windowedY = n4;
        }
        else {
            final int[] arr9 = { 0 };
            final int[] arr10 = { 0 };
            GLFW.glfwGetWindowPos(this.window, arr9, arr10);
            final int n5 = arr9[0];
            this.x = n5;
            this.windowedX = n5;
            final int n6 = arr10[0];
            this.y = n6;
            this.windowedY = n6;
        }
        GLFW.glfwMakeContextCurrent(this.window);
        GL.createCapabilities();
        this.setMode();
        this.refreshFramebufferSize();
        GLFW.glfwSetFramebufferSizeCallback(this.window, this::onFramebufferResize);
        GLFW.glfwSetWindowPosCallback(this.window, this::onMove);
        GLFW.glfwSetWindowSizeCallback(this.window, this::onResize);
        GLFW.glfwSetWindowFocusCallback(this.window, this::onFocus);
        GLFW.glfwSetCursorEnterCallback(this.window, this::onEnter);
    }
    
    public int getRefreshRate() {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        return GLX._getRefreshRate(this);
    }
    
    public boolean shouldClose() {
        return GLX._shouldClose(this);
    }
    
    public static void checkGlfwError(final BiConsumer<Integer, String> biConsumer) {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        try (final MemoryStack memoryStack2 = MemoryStack.stackPush()) {
            final PointerBuffer pointerBuffer4 = memoryStack2.mallocPointer(1);
            final int integer5 = GLFW.glfwGetError(pointerBuffer4);
            if (integer5 != 0) {
                final long long6 = pointerBuffer4.get();
                final String string8 = (long6 == 0L) ? "" : MemoryUtil.memUTF8(long6);
                biConsumer.accept(integer5, string8);
            }
        }
    }
    
    public void setIcon(final InputStream inputStream1, final InputStream inputStream2) {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        try (final MemoryStack memoryStack4 = MemoryStack.stackPush()) {
            if (inputStream1 == null) {
                throw new FileNotFoundException("icons/icon_16x16.png");
            }
            if (inputStream2 == null) {
                throw new FileNotFoundException("icons/icon_32x32.png");
            }
            final IntBuffer intBuffer6 = memoryStack4.mallocInt(1);
            final IntBuffer intBuffer7 = memoryStack4.mallocInt(1);
            final IntBuffer intBuffer8 = memoryStack4.mallocInt(1);
            final GLFWImage.Buffer buffer9 = GLFWImage.mallocStack(2, memoryStack4);
            final ByteBuffer byteBuffer10 = this.readIconPixels(inputStream1, intBuffer6, intBuffer7, intBuffer8);
            if (byteBuffer10 == null) {
                throw new IllegalStateException("Could not load icon: " + STBImage.stbi_failure_reason());
            }
            buffer9.position(0);
            buffer9.width(intBuffer6.get(0));
            buffer9.height(intBuffer7.get(0));
            buffer9.pixels(byteBuffer10);
            final ByteBuffer byteBuffer11 = this.readIconPixels(inputStream2, intBuffer6, intBuffer7, intBuffer8);
            if (byteBuffer11 == null) {
                throw new IllegalStateException("Could not load icon: " + STBImage.stbi_failure_reason());
            }
            buffer9.position(1);
            buffer9.width(intBuffer6.get(0));
            buffer9.height(intBuffer7.get(0));
            buffer9.pixels(byteBuffer11);
            buffer9.position(0);
            GLFW.glfwSetWindowIcon(this.window, buffer9);
            STBImage.stbi_image_free(byteBuffer10);
            STBImage.stbi_image_free(byteBuffer11);
        }
        catch (IOException iOException4) {
            Window.LOGGER.error("Couldn't set icon", (Throwable)iOException4);
        }
    }
    
    @Nullable
    private ByteBuffer readIconPixels(final InputStream inputStream, final IntBuffer intBuffer2, final IntBuffer intBuffer3, final IntBuffer intBuffer4) throws IOException {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        ByteBuffer byteBuffer6 = null;
        try {
            byteBuffer6 = TextureUtil.readResource(inputStream);
            byteBuffer6.rewind();
            return STBImage.stbi_load_from_memory(byteBuffer6, intBuffer2, intBuffer3, intBuffer4, 0);
        }
        finally {
            if (byteBuffer6 != null) {
                MemoryUtil.memFree((Buffer)byteBuffer6);
            }
        }
    }
    
    public void setErrorSection(final String string) {
        this.errorSection = string;
    }
    
    private void setBootErrorCallback() {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        GLFW.glfwSetErrorCallback(Window::bootCrash);
    }
    
    private static void bootCrash(final int integer, final long long2) {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        final String string4 = "GLFW error " + integer + ": " + MemoryUtil.memUTF8(long2);
        TinyFileDialogs.tinyfd_messageBox((CharSequence)"Minecraft", (CharSequence)(string4 + ".\n\nPlease make sure you have up-to-date drivers (see aka.ms/mcdriver for instructions)."), (CharSequence)"ok", (CharSequence)"error", false);
        throw new WindowInitFailed(string4);
    }
    
    public void defaultErrorCallback(final int integer, final long long2) {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        final String string5 = MemoryUtil.memUTF8(long2);
        Window.LOGGER.error("########## GL ERROR ##########");
        Window.LOGGER.error("@ {}", this.errorSection);
        Window.LOGGER.error("{}: {}", integer, string5);
    }
    
    public void setDefaultErrorCallback() {
        final GLFWErrorCallback gLFWErrorCallback2 = GLFW.glfwSetErrorCallback((GLFWErrorCallbackI)this.defaultErrorCallback);
        if (gLFWErrorCallback2 != null) {
            gLFWErrorCallback2.free();
        }
    }
    
    public void updateVsync(final boolean boolean1) {
        RenderSystem.assertThread(RenderSystem::isOnRenderThreadOrInit);
        GLFW.glfwSwapInterval((int)((this.vsync = boolean1) ? 1 : 0));
    }
    
    @Override
    public void close() {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        Callbacks.glfwFreeCallbacks(this.window);
        this.defaultErrorCallback.close();
        GLFW.glfwDestroyWindow(this.window);
        GLFW.glfwTerminate();
    }
    
    private void onMove(final long long1, final int integer2, final int integer3) {
        this.x = integer2;
        this.y = integer3;
    }
    
    private void onFramebufferResize(final long long1, final int integer2, final int integer3) {
        if (long1 != this.window) {
            return;
        }
        final int integer4 = this.getWidth();
        final int integer5 = this.getHeight();
        if (integer2 == 0 || integer3 == 0) {
            return;
        }
        this.framebufferWidth = integer2;
        this.framebufferHeight = integer3;
        if (this.getWidth() != integer4 || this.getHeight() != integer5) {
            this.eventHandler.resizeDisplay();
        }
    }
    
    private void refreshFramebufferSize() {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        final int[] arr2 = { 0 };
        final int[] arr3 = { 0 };
        GLFW.glfwGetFramebufferSize(this.window, arr2, arr3);
        this.framebufferWidth = arr2[0];
        this.framebufferHeight = arr3[0];
    }
    
    private void onResize(final long long1, final int integer2, final int integer3) {
        this.width = integer2;
        this.height = integer3;
    }
    
    private void onFocus(final long long1, final boolean boolean2) {
        if (long1 == this.window) {
            this.eventHandler.setWindowActive(boolean2);
        }
    }
    
    private void onEnter(final long long1, final boolean boolean2) {
        if (boolean2) {
            this.eventHandler.cursorEntered();
        }
    }
    
    public void setFramerateLimit(final int integer) {
        this.framerateLimit = integer;
    }
    
    public int getFramerateLimit() {
        return this.framerateLimit;
    }
    
    public void updateDisplay() {
        RenderSystem.flipFrame(this.window);
        if (this.fullscreen != this.actuallyFullscreen) {
            this.actuallyFullscreen = this.fullscreen;
            this.updateFullscreen(this.vsync);
        }
    }
    
    public Optional<VideoMode> getPreferredFullscreenVideoMode() {
        return this.preferredFullscreenVideoMode;
    }
    
    public void setPreferredFullscreenVideoMode(final Optional<VideoMode> optional) {
        final boolean boolean3 = !optional.equals(this.preferredFullscreenVideoMode);
        this.preferredFullscreenVideoMode = optional;
        if (boolean3) {
            this.dirty = true;
        }
    }
    
    public void changeFullscreenVideoMode() {
        if (this.fullscreen && this.dirty) {
            this.dirty = false;
            this.setMode();
            this.eventHandler.resizeDisplay();
        }
    }
    
    private void setMode() {
        RenderSystem.assertThread(RenderSystem::isInInitPhase);
        final boolean boolean2 = GLFW.glfwGetWindowMonitor(this.window) != 0L;
        if (this.fullscreen) {
            final Monitor dgq3 = this.screenManager.findBestMonitor(this);
            if (dgq3 == null) {
                Window.LOGGER.warn("Failed to find suitable monitor for fullscreen mode");
                this.fullscreen = false;
            }
            else {
                final VideoMode dgx4 = dgq3.getPreferredVidMode(this.preferredFullscreenVideoMode);
                if (!boolean2) {
                    this.windowedX = this.x;
                    this.windowedY = this.y;
                    this.windowedWidth = this.width;
                    this.windowedHeight = this.height;
                }
                this.x = 0;
                this.y = 0;
                this.width = dgx4.getWidth();
                this.height = dgx4.getHeight();
                GLFW.glfwSetWindowMonitor(this.window, dgq3.getMonitor(), this.x, this.y, this.width, this.height, dgx4.getRefreshRate());
            }
        }
        else {
            this.x = this.windowedX;
            this.y = this.windowedY;
            this.width = this.windowedWidth;
            this.height = this.windowedHeight;
            GLFW.glfwSetWindowMonitor(this.window, 0L, this.x, this.y, this.width, this.height, -1);
        }
    }
    
    public void toggleFullScreen() {
        this.fullscreen = !this.fullscreen;
    }
    
    private void updateFullscreen(final boolean boolean1) {
        RenderSystem.assertThread(RenderSystem::isOnRenderThread);
        try {
            this.setMode();
            this.eventHandler.resizeDisplay();
            this.updateVsync(boolean1);
            this.updateDisplay();
        }
        catch (Exception exception3) {
            Window.LOGGER.error("Couldn't toggle fullscreen", (Throwable)exception3);
        }
    }
    
    public int calculateScale(final int integer, final boolean boolean2) {
        int integer2;
        for (integer2 = 1; integer2 != integer && integer2 < this.framebufferWidth && integer2 < this.framebufferHeight && this.framebufferWidth / (integer2 + 1) >= 320 && this.framebufferHeight / (integer2 + 1) >= 240; ++integer2) {}
        if (boolean2 && integer2 % 2 != 0) {
            ++integer2;
        }
        return integer2;
    }
    
    public void setGuiScale(final double double1) {
        this.guiScale = double1;
        final int integer4 = (int)(this.framebufferWidth / double1);
        this.guiScaledWidth = ((this.framebufferWidth / double1 > integer4) ? (integer4 + 1) : integer4);
        final int integer5 = (int)(this.framebufferHeight / double1);
        this.guiScaledHeight = ((this.framebufferHeight / double1 > integer5) ? (integer5 + 1) : integer5);
    }
    
    public void setTitle(final String string) {
        GLFW.glfwSetWindowTitle(this.window, (CharSequence)string);
    }
    
    public long getWindow() {
        return this.window;
    }
    
    public boolean isFullscreen() {
        return this.fullscreen;
    }
    
    public int getWidth() {
        return this.framebufferWidth;
    }
    
    public int getHeight() {
        return this.framebufferHeight;
    }
    
    public int getScreenWidth() {
        return this.width;
    }
    
    public int getScreenHeight() {
        return this.height;
    }
    
    public int getGuiScaledWidth() {
        return this.guiScaledWidth;
    }
    
    public int getGuiScaledHeight() {
        return this.guiScaledHeight;
    }
    
    public int getX() {
        return this.x;
    }
    
    public int getY() {
        return this.y;
    }
    
    public double getGuiScale() {
        return this.guiScale;
    }
    
    @Nullable
    public Monitor findBestMonitor() {
        return this.screenManager.findBestMonitor(this);
    }
    
    public void updateRawMouseInput(final boolean boolean1) {
        InputConstants.updateRawMouseInput(this.window, boolean1);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    public static class WindowInitFailed extends SilentInitException {
        private WindowInitFailed(final String string) {
            super(string);
        }
    }
}
