package surface

import (
	"github.com/go-gl/gl/v4.1-core/gl"
)

// NV12 OpenGL surface
type NV12 struct {
	shaderProgram               uint32
	yTexture, uvTexture         int32
	yTextureName, uvTextureName uint32

	vbo, ebo uint32

	posAttrib uint32
	texAttrib uint32
}

// NewNV12 creates a nv12 OpenGL surface
func NewNV12() (*NV12, error) {
	s := &NV12{}

	// Vertex shader
	vertexSource := `
	attribute vec4 a_position;
	attribute vec2 a_texCoord;
	varying vec2 v_texCoord;
	void main() {
		gl_Position = a_position;
		v_texCoord = a_texCoord;
	}
	` + "\x00"

	// Fragment shader
	fragmentSource := `
	varying vec2 v_texCoord;
	uniform sampler2D y_texture;
	uniform sampler2D uv_texture;

	void main (void){
		float r, g, b, y, u, v;
		y = texture2D(y_texture, v_texCoord).r;
		u = texture2D(uv_texture, v_texCoord).r - 0.5;
		v = texture2D(uv_texture, v_texCoord).g - 0.5;

		r = y + 1.13983*v;
		g = y - 0.39465*u - 0.58060*v;
		b = y + 2.03211*u;

		gl_FragColor = vec4(r, g, b, 1.0);
	}
	` + "\x00"

	// Create and compile shaders
	var err error
	s.shaderProgram, err = loadProgram(vertexSource, fragmentSource)
	if err != nil {
		return nil, err
	}

	// Specify the layout of the vertex data
	s.posAttrib = uint32(gl.GetAttribLocation(s.shaderProgram, gl.Str("a_position\x00")))
	s.texAttrib = uint32(gl.GetAttribLocation(s.shaderProgram, gl.Str("a_texCoord\x00")))

	// Vertex Buffer object
	gl.GenBuffers(1, &s.vbo)
	// Element array object
	gl.GenBuffers(1, &s.ebo)

	// Y Texture
	gl.Enable(gl.TEXTURE_2D)
	s.yTexture = gl.GetUniformLocation(s.shaderProgram, gl.Str("y_texture\x00"))
	gl.GenTextures(1, &s.yTextureName)

	// UV Texture
	gl.Enable(gl.TEXTURE_2D)
	s.uvTexture = gl.GetUniformLocation(s.shaderProgram, gl.Str("uv_texture\x00"))
	gl.GenTextures(1, &s.uvTextureName)

	// Black background
	gl.ClearColor(0.0, 0.0, 0.0, 0.0)

	return s, nil
}

// Draw ...
func (s *NV12) Draw(width, height int, data []byte) error {
	yPlane := data[:width*height]
	uvPlane := data[width*height:]

	gl.Clear(gl.COLOR_BUFFER_BIT)

	// Use the program object
	gl.UseProgram(s.shaderProgram)

	// Load the vertex position
	gl.BindBuffer(gl.ARRAY_BUFFER, s.vbo)
	gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW)
	gl.VertexAttribPointer(s.posAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(0*4))
	gl.EnableVertexAttribArray(s.posAttrib)

	// Load the texture coordinate
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, s.ebo)
	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indices)*4, gl.Ptr(indices), gl.STATIC_DRAW)
	gl.VertexAttribPointer(s.texAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(2*4))
	gl.EnableVertexAttribArray(s.texAttrib)

	// Y
	gl.ActiveTexture(gl.TEXTURE1)
	gl.Uniform1i(s.yTexture, 1)
	gl.BindTexture(gl.TEXTURE_2D, s.yTextureName)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, gl.Ptr(yPlane))
	// texture filtering params
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	// texture wrapping params
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	// UV
	gl.ActiveTexture(gl.TEXTURE2)
	gl.Uniform1i(s.uvTexture, 2)
	gl.BindTexture(gl.TEXTURE_2D, s.uvTextureName)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RG, int32(width/2), int32(height/2), 0, gl.RG, gl.UNSIGNED_BYTE, gl.Ptr(uvPlane))
	// texture filtering params
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	// texture wrapping params
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	// Draw
	gl.DrawElements(gl.TRIANGLES, int32(len(indices)), gl.UNSIGNED_INT, nil)

	return nil
}
