package org.hirw.game;

import static org.lwjgl.opengl.GL20.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.hirw.game.util.Log;

public class Shader {
  private enum ShaderType {
    FRAG,
    VERT
  }

  private static final EnumMap<ShaderType, Integer> SHADERS =
      new EnumMap<>(
          Map.of(
              ShaderType.FRAG, GL_FRAGMENT_SHADER,
              ShaderType.VERT, GL_VERTEX_SHADER));

  protected static final String DEFAULT_FRAG_PATH = "assets/shaders/default.frag.glsl";
  protected static final String DEFAULT_VERT_PATH = "assets/shaders/default.vert.glsl";

  @Getter private String vertexSource;
  @Getter private String fragmentSource;
  @Getter @Setter private int vertexID;
  @Getter @Setter private int fragmentID;
  @Getter @Setter private int shaderProgramID;

  public Shader(String fragPath, String vertPath) {
    this.fragmentSource = readFromFile(fragPath);
    this.vertexSource = readFromFile(vertPath);
  }

  public Shader(String path) {
    this(path + ".frag.glsl", path + ".vert.glsl");
  }

  public Shader() {
    this(DEFAULT_FRAG_PATH, DEFAULT_VERT_PATH);
  }

  public void init() {
    compileShader(ShaderType.FRAG);
    compileShader(ShaderType.VERT);
    createProgram();
  }

  public void use() {
    glUseProgram(getShaderProgramID());
  }

  public void detach() {
    glUseProgram(0);
  }

  public void update() {}

  private void compileShader(ShaderType shaderType) {
    int shaderID = glCreateShader(SHADERS.get(shaderType));

    switch (shaderType) {
      case ShaderType.FRAG -> {
        setFragmentID(shaderID);
        glShaderSource(shaderID, getFragmentSource());
      }
      case ShaderType.VERT -> {
        setVertexID(shaderID);
        glShaderSource(shaderID, getVertexSource());
      }
    }

    glCompileShader(shaderID);

    if (glGetShaderi(shaderID, GL_COMPILE_STATUS) == GL_FALSE) {
      int len = glGetShaderi(shaderID, GL_INFO_LOG_LENGTH);
      Log.error(
          "Shader",
          String.format(
              "Failed to compile %s shader: %s",
              shaderType.toString(), glGetShaderInfoLog(shaderID, len)));
    }
  }

  private void createProgram() {
    int id = glCreateProgram();
    glAttachShader(id, getVertexID());
    glAttachShader(id, getFragmentID());
    glLinkProgram(id);

    int success = glGetProgrami(id, GL_LINK_STATUS);
    if (success == GL_FALSE) {
      int len = glGetProgrami(id, GL_INFO_LOG_LENGTH);

      Log.error(
          "Shader",
          String.format("Failed to create Shader Program: %s", glGetShaderInfoLog(id, len)));
    } else {
      Log.success("Shader", "Shader Program created.");
    }

    setShaderProgramID(id);
  }

  private String readFromFile(String stringFilePath) {
    String source = "";

    try {
      Path filePath = Paths.get(stringFilePath);
      source = Files.readString(filePath);

    } catch (NoSuchFileException | InvalidPathException e) {
      Log.error("Shader", "Couldn't open file (probably a bad path): " + stringFilePath);
    } catch (IOException e) {
      Log.error("Shader", "An IO Exception occured while reading from file: " + stringFilePath);
    }

    return source;
  }
}
