package surface

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

// YUYV OpenGL surface
type YUYV struct {
	shaderProgram uint32
	yTexture      int32
	yTextureName  uint32

	vbo, ebo uint32

	posAttrib uint32
	texAttrib uint32
}

// NewYUV422 creates a yuv422 OpenGL surface
func NewYUYV() (*YUYV, error) {
	s := &YUYV{}

	// 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 YUYV
	fragmentSource := `
		#version 400
		varying vec2 v_texCoord;
		uniform sampler2D y_texture;

		mat3 csc = mat3(
			1.,   0.,       1.13983,
			1.,  -0.39465, -0.58060,
			1.,   2.03211,  0.);

		void main (void){
			// Fetch Luma Y - Use GPU native bilinear interpolation
			float y = texture(y_texture, v_texCoord).r;
			
			// Process YU & YV texel size
			vec2 textureSize = textureSize(y_texture, 0);
			vec2 texelSize0 = 0./textureSize;
			vec2 texelSize1 = 1./textureSize;
			vec2 texelSize2 = 2./textureSize;
			vec2 texelSize3 = 3./textureSize;

			// YU/YV texels
			// YU00 YV10 YU20 YV30 ...
			// YU01 YV11 YU21 YV31 ...

			// YU00 top left coord
			vec2 coord = floor(v_texCoord*textureSize / vec2(2.,1.)) * vec2(2.,1.);
			// YU00 center coord
			coord += .5;
			// YU00 normalized coord [0..1]
			coord /= textureSize;
			
			// Fetch Chroma Uxy
			float u00 = texture(y_texture, coord + vec2(texelSize0.x, texelSize0.y)).g;
			float u20 = texture(y_texture, coord + vec2(texelSize2.x, texelSize0.y)).g;
			float u01 = texture(y_texture, coord + vec2(texelSize0.x, texelSize1.y)).g;
			float u21 = texture(y_texture, coord + vec2(texelSize2.x, texelSize1.y)).g;

			// Fetch Chroma Vxy
			float v10 = texture(y_texture, coord + vec2(texelSize1.x, texelSize0.y)).g;
			float v30 = texture(y_texture, coord + vec2(texelSize3.x, texelSize0.y)).g;
			float v11 = texture(y_texture, coord + vec2(texelSize1.x, texelSize1.y)).g;
			float v31 = texture(y_texture, coord + vec2(texelSize3.x, texelSize1.y)).g;

			// Interpolation weight
			vec2 w = fract((v_texCoord*textureSize) / vec2(2.,1.));

			// YUV
			vec3 yuv;
			yuv.r = y;
			yuv.g = mix(
				mix(u00, u20, w.x),
				mix(u01, u21, w.x), w.y
			) -0.5;
			yuv.b = mix(
				mix(v10, v30, w.x),
				mix(v11, v31, w.x), w.y
			) -0.5;

			// RGBA
			gl_FragColor = vec4(yuv*csc, 1.);
		}
		` + "\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)

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

	return s, nil
}

// Draw ...
func (s *YUYV) Draw(width, height int, data []byte) error {
	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.VertexAttribPointerWithOffset(s.posAttrib, 2, gl.FLOAT, false, 4*4, 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.VertexAttribPointerWithOffset(s.texAttrib, 2, gl.FLOAT, false, 4*4, 2*4)
	gl.EnableVertexAttribArray(s.texAttrib)

	// Y
	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, s.yTextureName)
	gl.Uniform1i(s.yTexture, 0)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RG, int32(width), int32(height), 0, gl.RG, gl.UNSIGNED_BYTE, gl.Ptr(data))
	// 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
}
