-- inst_scene.lua
inst_scene = {}

local openGL = require("opengl")
local ffi = require("ffi")
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 u = 0.0
local vao = 0
local prog = 0
local vbos = {}

local num_cubes = 1

local basic_vert = [[
#version 330

in vec4 vPosition;
in vec4 vColor;

out vec3 vfColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;

uniform int num_cubes;
uniform float u;
uniform vec3 spline_points[5];

vec3 GetCatmullRomPoint(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t)
{
    float t2 = t*t;
    float t3 = t*t*t;
    return 0.5*(
            (2.*p1) +
            (-p0 + p2)*t +
            (2.*p0 - 5.*p1 + 4.*p2 - p3)*t2 +
            (-p0 + 3.*p1 - 3.*p2 + p3)*t3
            );
}
void main()
{
    vfColor = vColor.xyz;
    vec3 pos3 = vPosition.xyz;

    float t = float(gl_InstanceID) / float(num_cubes-1);

    vec3 p0 = spline_points[0];
    vec3 p1 = spline_points[1];
    vec3 p2 = spline_points[2];
    vec3 p3 = spline_points[3];
    vec3 p4 = spline_points[4];

    vec3 sp0 = GetCatmullRomPoint(p0, p1, p2, p3, t+u);
    vec3 sp1 = GetCatmullRomPoint(p1, p2, p3, p4, t+u-1.);
    vec3 sp = sp0;
    if (t+u > 1.)
        sp = sp1;

    pos3 *= 0.7 + 0.3*t;
    pos3 += sp;

    vec4 pos4 = vec4(pos3, 1.);
    gl_Position = prmtx * mvmtx * pos4;
}
]]

local basic_frag = [[
#version 330

in vec3 vfColor;
out vec4 fragColor;

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


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

    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)
    table.insert(vbos, vvbo)

    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.STATIC_DRAW)
    gl.VertexAttribPointer(vcol_loc, 3, GL.FLOAT, GL.FALSE, 0, nil)
    table.insert(vbos, cvbo)

    gl.EnableVertexAttribArray(vpos_loc)
    gl.EnableVertexAttribArray(vcol_loc)

    local quads = glUintv(6*6, {
        0,3,2, 1,0,2,
        4,5,6, 7,4,6,
        1,2,6, 5,1,6,
        2,3,7, 6,2,7,
        3,0,4, 7,3,4,
        0,1,5, 4,0,5
    })
    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)
    table.insert(vbos, qvbo)
end

function inst_scene.initGL()
    vbos = {}
    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,
        fsrc = basic_frag,
        })

    init_inst_attributes()
    gl.BindVertexArray(0)
end

function inst_scene.exitGL()
    gl.BindVertexArray(vao)
    for _,v in pairs(vbos) do
        gl.DeleteBuffers(1,v)
    end
    vbos = {}
    gl.DeleteProgram(prog)

    local vaoId = ffi.new("GLuint[1]", vao)
    gl.DeleteVertexArrays(1, vaoId)
end

local function inst_draw_color_cube_row()
    local nc_loc = gl.GetUniformLocation(prog, "num_cubes")
    gl.Uniform1i(nc_loc, num_cubes)
    local u_loc = gl.GetUniformLocation(prog, "u")
    gl.Uniform1f(u_loc, math.fmod(u, 1.0))

    local spl_pts = glFloatv(3*5, {
        -5., 5., 0.,
        -5., 0., 0.,
        2., 0., 0.,
        2., 5., 0.,
        6., 5., 0.,
        })
    local sp_loc = gl.GetUniformLocation(prog, "spline_points")
    gl.Uniform3fv(sp_loc, 5, spl_pts)

    gl.DrawElementsInstanced(GL.TRIANGLES, 6*3*2, GL.UNSIGNED_INT, nil, num_cubes)
end

function inst_scene.render_for_one_eye(m, proj)
    --glh_rotate(m, g*0.1, 0,1,0)
    --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")

    gl.BindVertexArray(vao)
    gl.UseProgram(prog)
    gl.UniformMatrix4fv(umv_loc, 1, GL.FALSE, glFloatv(16, m))
    gl.UniformMatrix4fv(upr_loc, 1, GL.FALSE, glFloatv(16, proj))
    inst_draw_color_cube_row()
    gl.UseProgram(0)
    gl.BindVertexArray(0)
end

function inst_scene.timestep(absTime, dt)
    g = absTime
    u = 0.5+0.5*math.sin(g)
    num_cubes = 32
end

return inst_scene
