-- dots_scene.lua
dots_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 pt_dim = 20


local basic_vert = [[
#version 330

in vec4 vPosition;

uniform mat4 mvmtx;
uniform mat4 prmtx;

void main()
{
    vec3 pos3 = vPosition.xyz;
    vec4 pos4 = vec4(pos3, 1.);
    gl_Position = pos4; // apply matrices in geom shader
}
]]

local basic_geom = [[
#version 330

layout(points) in;
layout(line_strip, max_vertices = 2) out;

out vec3 gfColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;

vec3 vectorField(in vec3 pos)
{
    vec3 wc = pos - vec3(2.);
    float f = 1.;
    vec2 planeCenter = vec2(2.) + 1.*vec2(cos(f*wc.z), sin(f*wc.z));
    vec3 dp = vec3(wc.xy-planeCenter, 0.);

    float mag = 1. - smoothstep(0.,3.,length(dp));
    vec3 pull = -dp;
    pull.z = 1.;
    return mag * pull;

    //return normalize(pos - vec3(2.)); // radial
}

void main() {
    vec4 pos = gl_in[0].gl_Position;
    vec3 off = vectorField(pos.xyz);

    gl_Position = prmtx * mvmtx * pos;
    gfColor = abs(off);
    EmitVertex();

    gl_Position = prmtx * mvmtx * (pos + vec4(.5 * off,0.));
    gfColor = abs(off);
    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 pts = {}
    for k=0,pt_dim-1 do
        local kx = k*pt_dim*pt_dim*3
        for j=0,pt_dim-1 do
            local jx = kx + j*pt_dim*3
            for i=0,pt_dim-1 do
                local ix = jx + i*3
                local sz = 8
                pts[ix] = sz*(i-1)/pt_dim
                pts[ix+1] = sz*(j-1)/pt_dim
                pts[ix+2] = sz*(k-1)/pt_dim
            end
        end
    end
    local verts = glFloatv(3*pt_dim*pt_dim*pt_dim, pts)

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

    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)

    sf.CHECK_GL_ERROR()
    gl.EnableVertexAttribArray(vpos_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 dots_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,
        fsrc = basic_frag,
        gsrc = basic_geom,
        })

    init_cube_attributes()
    gl.BindVertexArray(0)
end

function draw_color_cube()
    gl.BindVertexArray(vao)
    gl.DrawArrays(GL.POINTS, 0, 3*pt_dim*pt_dim*pt_dim)
    gl.BindVertexArray(0)
end

function dots_scene.render_for_one_eye(mview, proj)
    -- Cast the cdata ptr to a table for further manipulation here in Lua
    local m0 = ffi.cast("float*", mview)
    -- Once we have done so, the table is 0-indexed. here we clumsily jam it into
    -- a Lua-style, 1-indexed table.
    local m = {}
    for i=0,15 do m[i+1] = m0[i] end

    --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")

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

    draw_color_cube()
    sf.CHECK_GL_ERROR()

    gl.UseProgram(0)
end

function dots_scene.timestep(dt)
    g = g + dt
end

return dots_scene