package org.hirw.game;

import static org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray;
import static org.lwjgl.opengl.ARBVertexArrayObject.glGenVertexArrays;
import static org.lwjgl.opengl.GL20.*;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.BufferUtils;

public class Mesh {
  @Getter @Setter private int vaoID, vboID, eboID;
  @Getter protected Shader shader;
  @Getter float[] vertices;
  @Getter int[] elements;

  protected final int POSITION_SIZE = 3;
  protected final int RGBA_SIZE = 4;
  protected final int FLOAT_SIZE_IN_BYTES = Float.SIZE / Byte.SIZE;

  protected static final float[] DEFAULT_VERTEX_ARRAY = {
    0.5f, -0.5f, 0.0f, /*           */ 1.0f, 0.0f, 0.0f, 1.0f,
    -0.5f, 0.5f, 0.0f, /*           */ 0.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.5f, 0.0f, /*            */ 0.0f, 0.0f, 1.0f, 1.0f,
    -0.5f, -0.5f, 0.0f, /*          */ 1.0f, 1.0f, 0.0f, 1.0f,
  };

  protected static final int[] DEFAULT_ELEMENT_ARRAY = {
    2, 1, 0,
    0, 1, 3
  };

  public Mesh(Shader shader, float[] vertexArray, int[] elementArray) {
    this.shader = shader;
    this.vaoID = -1;
    this.vboID = -1;
    this.eboID = -1;
    this.vertices = vertexArray;
    this.elements = elementArray;
  }

  public Mesh(Shader shader) {
    this(shader, DEFAULT_VERTEX_ARRAY, DEFAULT_ELEMENT_ARRAY);
  }

  public void init() {
    initialiseVertexArrayObject();
  }

  public void draw() {
    getShader().use();
    glDraw();
    getShader().detach();
  }

  public void log() {
    System.out.println(this.toString());
  }

  public String toString() {
    return String.format(
        "VAO: %d, VBO: %d, EBO: %d, ShaderProgram: %d",
        getEboID(), getVboID(), getEboID(), getShader().getShaderProgramID());
  }

  protected void glDraw() {
    glBindVertexArray(this.vaoID);
    glDrawElements(GL_TRIANGLES, getElements().length, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
  }

  private void initialiseVertexArrayObject() {
    setVaoID(glGenVertexArrays());
    glBindVertexArray(getVaoID());

    initialiseVertexBufferObject();
    initialiseElementBufferObject();

    initialiseAttribPointers();
  }

  protected int initialiseAttribPointers() {
    int vertexSizeInBytes = calculateVertexSizeInBytes();

    glVertexAttribPointer(0, POSITION_SIZE, GL_FLOAT, false, vertexSizeInBytes, 0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(
        1, RGBA_SIZE, GL_FLOAT, false, vertexSizeInBytes, POSITION_SIZE * FLOAT_SIZE_IN_BYTES);
    glEnableVertexAttribArray(1);

    return vertexSizeInBytes;
  }

  protected int calculateVertexSizeInBytes() {
    return (POSITION_SIZE + RGBA_SIZE) * FLOAT_SIZE_IN_BYTES;
  }

  private void initialiseVertexBufferObject() {
    FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(getVertices().length);
    vertexBuffer.put(getVertices()).flip();

    setVboID(glGenBuffers());
    glBindBuffer(GL_ARRAY_BUFFER, getVboID());
    glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);
  }

  private void initialiseElementBufferObject() {
    IntBuffer elementBuffer = BufferUtils.createIntBuffer(getElements().length);
    elementBuffer.put(getElements()).flip();

    setEboID(glGenBuffers());
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getEboID());
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBuffer, GL_STATIC_DRAW);
  }
}
