package org.hirw.game;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.*;

import lombok.Getter;
import lombok.Setter;
import org.hirw.game.util.Time;
import org.lwjgl.Version;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;

public class Window {
  private int width, height;
  @Getter private String title;
  @Getter private long glfwWindow;

  private static Window window = null;

  @Getter @Setter private Shader shader;
  @Getter @Setter private Mesh mesh;

  private Window() {
    this.width = 1280;
    this.height = 720;
    this.title = "game";
  }

  public static Window get() {
    if (window == null) {
      window = new Window();
    }
    return window;
  }

  public void blastOff() {
    init();
    loop();
    cleanUp();
  }

  private void init() {
    logVersion();
    createWindow();
    createShader();
    createMesh();
    SceneManager.init();
  }

  private void logVersion() {
    System.out.println("LWJGL Version: " + Version.getVersion());
  }

  private void createWindow() {
    GLFWErrorCallback.createPrint(System.err).set();
    if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");

    glfwDefaultWindowHints();
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

    glfwWindow = glfwCreateWindow(this.width, this.height, getTitle(), NULL, NULL);
    if (glfwWindow == NULL) throw new RuntimeException("Failed to create the GLFW window");

    glfwSetCursorPosCallback(glfwWindow, Mouse::cursorPositionCallback);
    glfwSetMouseButtonCallback(glfwWindow, Mouse::mouseButtonCallback);
    glfwSetKeyCallback(glfwWindow, Keyboard::keyCallback);

    glfwSetWindowTitle(glfwWindow, this.title);
    glfwMakeContextCurrent(glfwWindow);
    glfwSwapInterval(1);
    glfwShowWindow(glfwWindow);

    GL.createCapabilities();

    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
  }

  private void createShader() {
    Shader shader = new Shader();
    setShader(shader);
    shader.init();
  }

  private void createMesh() {
    Mesh mesh = new Mesh(getShader());
    setMesh(mesh);
    mesh.init();
  }

  private void loop() {
    while (!glfwWindowShouldClose(glfwWindow)) {
      Time.update();
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      getMesh().draw();
      SceneManager.update();
      glfwSwapBuffers(glfwWindow);
      glfwPollEvents();
    }
  }

  private void cleanUp() {
    glfwFreeCallbacks(glfwWindow);
    glfwDestroyWindow(glfwWindow);
    glfwTerminate();
    glfwSetErrorCallback(null).free();
  }
}
