package shader

import (
	"errors"
	"strings"

	"github.com/go-gl/gl/v4.6-core/gl"
)

type Shader struct {
	program  uint32
	uniforms map[string]uint32
}

func (s *Shader) Use() {
	gl.UseProgram(s.program)
}

func (s *Shader) Delete() {
	gl.DeleteProgram(s.program)
}

func compileShader(source string, shaderType uint32) (uint32, error) {
	shader := gl.CreateShader(shaderType)

	csource, free := gl.Strs(source)
	gl.ShaderSource(shader, 1, csource, nil)
	free()
	gl.CompileShader(shader)

	var status int32
	gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
	if status == gl.FALSE {
		var logLength int32
		gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength)

		log := strings.Repeat("\x00", int(logLength+1))
		gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log))

		return 0, errors.New(log)
	}

	return shader, nil
}

func New(vertexSource string, fragmantSource string) (shader Shader, _ error) {
	shader.uniforms = make(map[string]uint32)

	vertexShader, err := compileShader(vertexSource, gl.VERTEX_SHADER)
	if err != nil {
		return shader, err
	}

	fragmantShader, err := compileShader(fragmantSource, gl.FRAGMENT_SHADER)
	if err != nil {
		return shader, err
	}

	shader.program = gl.CreateProgram()
	gl.AttachShader(shader.program, vertexShader)
	gl.AttachShader(shader.program, fragmantShader)
	gl.LinkProgram(shader.program)
	defer gl.DeleteShader(vertexShader)
	defer gl.DeleteShader(fragmantShader)

	var status int32
	gl.GetProgramiv(shader.program, gl.LINK_STATUS, &status)
	if status == gl.FALSE {
		var logLength int32
		gl.GetProgramiv(shader.program, gl.INFO_LOG_LENGTH, &logLength)

		log := strings.Repeat("\x00", int(logLength+1))
		gl.GetProgramInfoLog(shader.program, logLength, nil, gl.Str(log))

		return shader, errors.New(log)
	}

	return shader, nil
}
