-- tess_scene.lua
tess_scene = {}

local openGL = require("opengl")
local ffi = require("ffi")
local mm = require("util.matrixmath")
local sf = require("util.shaderfunctions")

-- Types from:
-- https://github.com/nanoant/glua/blob/master/init.lua
local glIntv     = ffi.typeof('GLint[?]')
local glUintv    = ffi.typeof('GLuint[?]')
local glCharv    = ffi.typeof('GLchar[?]')
local glSizeiv   = ffi.typeof('GLsizei[?]')
local glFloatv   = ffi.typeof('GLfloat[?]')
local glConstCharpp = ffi.typeof('const GLchar *[1]')

local g = 0.0
local vao = 0
local prog = 0

local tess_vals = {8,2,4,6}

local fill = 0
function toggle_fill()
    fill = 1 - fill
end

local basic_vert = [[
#version 410 core

in vec4 vPosition;
in vec4 vColor;

out vec3 vcColor;

void main()
{
    vcColor = vColor.xyz;
    gl_Position = vec4(2.*vPosition.xyz, 1.);
}
]]

--[[ Tessellation stuff
http://ogldev.atspace.co.uk/www/tutorial30/tutorial30.html
http://prideout.net/blog/?p=48
]]

local basic_tc = [[
#version 410 core

layout(vertices = 3) out;

in vec3 vcColor[];
out vec3 ceColor[];

uniform vec4 u_tessval;

#define ID gl_InvocationID

void main(void)
{
    ceColor[ID] = vcColor[ID];

    gl_TessLevelInner[0] = u_tessval.x;
    gl_TessLevelOuter[0] = u_tessval.y;
    gl_TessLevelOuter[1] = u_tessval.z;
    gl_TessLevelOuter[2] = u_tessval.w;
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}
]]

local basic_te = [[
#version 410 core

layout(triangles, equal_spacing, ccw) in;

in vec3 ceColor[];
out vec3 egColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;

void main()
{
    vec3 p0 = gl_TessCoord.x * ceColor[0];
    vec3 p1 = gl_TessCoord.y * ceColor[1];
    vec3 p2 = gl_TessCoord.z * ceColor[2];
    egColor = normalize(p0 + p1 + p2);

    gl_Position.xyzw =  prmtx * mvmtx * (
                        gl_in[0].gl_Position.xyzw * gl_TessCoord.x +
                        gl_in[1].gl_Position.xyzw * gl_TessCoord.y +
                        gl_in[2].gl_Position.xyzw * gl_TessCoord.z);
}
]]

local basic_geo = [[
#version 410 core

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

in vec3 egColor[3];
out vec3 gfColor;

void main(void)
{
    gfColor = egColor[0];
    gl_Position = gl_in[0].gl_Position;
    EmitVertex();

    gfColor = egColor[1];
    gl_Position = gl_in[1].gl_Position;
    EmitVertex();

    gfColor = egColor[2];
    gl_Position = gl_in[2].gl_Position;
    EmitVertex();
    //gl_Position = gl_in[3].gl_Position;
    //EmitVertex();
    
    EndPrimitive();
}
]]

local basic_frag = [[
#version 330

in vec3 gfColor;
out vec4 fragColor;

void main()
{
    fragColor = vec4(gfColor, 1.0);
}
]]


function init_cube_attributes()
    local verts = glFloatv(3*3, {
        0,0,1,
        1,0,0,
        0,1,0,
        })

    local vpos_loc = gl.GetAttribLocation(prog, "vPosition")
    local vcol_loc = gl.GetAttribLocation(prog, "vColor")

    local vvbo = glIntv(0)
    gl.GenBuffers(1, vvbo)
    gl.BindBuffer(GL.ARRAY_BUFFER, vvbo[0])
    gl.BufferData(GL.ARRAY_BUFFER, ffi.sizeof(verts), verts, GL.STATIC_DRAW)
    gl.VertexAttribPointer(vpos_loc, 3, GL.FLOAT, GL.FALSE, 0, nil)

    local cvbo = glIntv(0)
    gl.GenBuffers(1, cvbo)
    gl.BindBuffer(GL.ARRAY_BUFFER, cvbo[0])
    gl.BufferData(GL.ARRAY_BUFFER, ffi.sizeof(verts), verts, GL.DYNAMIC_DRAW)
    sf.CHECK_GL_ERROR()
    gl.VertexAttribPointer(vcol_loc, 3, GL.FLOAT, GL.FALSE, 0, nil)

    sf.CHECK_GL_ERROR()
    gl.EnableVertexAttribArray(vpos_loc)
    gl.EnableVertexAttribArray(vcol_loc)

    local quads = glUintv(3, {
        1,0,2,
    })
    local qvbo = glIntv(0)
    gl.GenBuffers(1, qvbo)
    gl.BindBuffer(GL.ELEMENT_ARRAY_BUFFER, qvbo[0])
    gl.BufferData(GL.ELEMENT_ARRAY_BUFFER, ffi.sizeof(quads), quads, GL.STATIC_DRAW)
    sf.CHECK_GL_ERROR()
end

function tess_scene.initGL()
    local vaoId = ffi.new("int[1]")
    gl.GenVertexArrays(1, vaoId)
    vao = vaoId[0]
    gl.BindVertexArray(vao)

    prog = sf.make_shader_from_source({
        vsrc = basic_vert,
        tcsrc = basic_tc,
        tesrc = basic_te,
        gsrc = basic_geo,
        fsrc = basic_frag,
        })

    init_cube_attributes()
    gl.BindVertexArray(0)
end

function tess_scene.exitGL()
    gl.DeleteProgram(prog)
    
    local vaoId = ffi.new("GLuint[1]", vao)
    gl.DeleteVertexArrays(1, vaoId)
end

function draw_color_cube()
    gl.BindVertexArray(vao)
    gl.PatchParameteri(GL.PATCH_VERTICES, 3)
    gl.DrawElements(GL.PATCHES, 3, GL.UNSIGNED_INT, nil)
    gl.BindVertexArray(0)
end

function tess_scene.render_for_one_eye(m, proj)
    --glh_rotate(m, g*0.1, 0,1,0)
    mm.glh_translate(m, -0.5, 0.1*math.sin(g), -0.5)

    local umv_loc = gl.GetUniformLocation(prog, "mvmtx")
    local upr_loc = gl.GetUniformLocation(prog, "prmtx")
    local utf_loc = gl.GetUniformLocation(prog, "u_tessval")

    if fill == 1 then
        gl.PolygonMode(GL.FRONT_AND_BACK, GL.FILL)
    else
        gl.PolygonMode(GL.FRONT_AND_BACK, GL.LINE)
    end

    gl.UseProgram(prog)
    gl.UniformMatrix4fv(umv_loc, 1, GL.FALSE, glFloatv(16, m))
    gl.UniformMatrix4fv(upr_loc, 1, GL.FALSE, glFloatv(16, proj))

    -- TODO: Why does the fv call cause crashes?
    --gl.Uniform4fv(utf_loc, 4, glFloatv(4, tess_vals))
    gl.Uniform4f(utf_loc,
        tess_vals[1],
        tess_vals[2],
        tess_vals[3],
        tess_vals[4]
        )

    draw_color_cube()
    sf.CHECK_GL_ERROR()

    gl.UseProgram(0)
end

function tess_scene.timestep(dt)
    g = g + dt
    tess_vals[1] = 5 + 4*math.sin(10*g)
    tess_vals[2] = 2 + 2*math.sin(5*g)
    tess_vals[3] = 4 + 4*math.sin(2*g)
    tess_vals[4] = 6 + 6*math.sin(1*g)
end

return tess_scene
