-- trihex_scene.lua
trihex_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 vao = 0
local prog = 0
local vbos = {}

-- TODO: clean up global namespace
tri_var = 345
jparmx = 0.41
jparmy = 0.31
joffx = -0.03
joffy = 0.19
jscalex = 0.6
jscaley = 1.0

local umv_loc
local upr_loc
local jp_loc 
local jo_loc 
local js_loc 

local flake_vert = [[
#version 410 core

in vec4 vPosition;
in vec4 vColor;

out vec3 vfColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;

void main()
{
    vfColor = vColor.xyz;
    gl_Position = prmtx * mvmtx * vec4(3.*vPosition.xyz, 1.);
}
]]

local flake_frag = [[
#version 330

in vec3 vfColor;
out vec4 fragColor;

uniform vec2 jparam;
uniform vec2 joff;
uniform vec2 jscale;

const int max_iterations = 255;

vec2 complex_square( vec2 v ) {
    return vec2(
        v.x * v.x - v.y * v.y,
        v.x * v.y * 2.0
    );
}

float fractal(vec2 c, vec2 v)
{
    int count = max_iterations;
    
    for ( int i = 0 ; i < max_iterations; i++ ) {
        v = c + complex_square( v );
        if ( dot( v, v ) > 4.0 ) {
            count = i;
            break;
        }
    }
    return float(count);
}

void main()
{
    float scale = 0.01;
    vec2 uv = vfColor.xy;
    float f = fractal(jparam,jscale*uv+joff) * scale;
    float a = clamp(0.,1., 1.4*(f - .3));
    fragColor = vec4(vec3(f), f);
}
]]

local function init_shader()
    prog = sf.make_shader_from_source({
        vsrc = flake_vert,
        fsrc = flake_frag,
        })

    umv_loc = gl.GetUniformLocation(prog, "mvmtx")
    upr_loc = gl.GetUniformLocation(prog, "prmtx")
    jp_loc = gl.GetUniformLocation(prog, "jparam")
    jo_loc = gl.GetUniformLocation(prog, "joff")
    js_loc = gl.GetUniformLocation(prog, "jscale")
end

local function init_flake_attributes()
    local s = math.sqrt(3)
    local x = 1.5
    local y = s * 0.5
    local verts = glFloatv(2*13, {
        0,0,
        0,s,
        1,s,
        x,y,
        2,0,
        x,-y,
        1,-s,
        0,-s,
        -1,-s,
        -x,-y,
        -2,0,
        -x,y,
        -1,s,
        })
    local cols = glFloatv(3*13, {
        0,0,0,
        1,1,0,
        0,1,0,
        1,1,0,
        0,1,0,
        1,1,0,
        0,1,0,
        1,1,0,
        0,1,0,
        1,1,0,
        0,1,0,
        1,1,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, 2, GL.FLOAT, GL.FALSE, 0, nil)
    table.insert(vbos, vvbo)

    -- TODO: keep all our VBOs in a table so we can delete them
    local cvbo = glIntv(0)
    gl.GenBuffers(1, cvbo)
    gl.BindBuffer(GL.ARRAY_BUFFER, cvbo[0])
    gl.BufferData(GL.ARRAY_BUFFER, ffi.sizeof(cols), cols, GL.STATIC_DRAW)
    sf.CHECK_GL_ERROR()
    gl.VertexAttribPointer(vcol_loc, 3, GL.FLOAT, GL.FALSE, 0, nil)
    table.insert(vbos, cvbo)

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

    local quads = glUintv(3*12, {
        0,1,2,
        0,2,3,
        0,3,4,
        0,4,5,
        0,5,6,
        0,6,7,
        0,7,8,
        0,8,9,
        0,9,10,
        0,10,11,
        0,11,12,
        0,12,1,
    })
    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)
    sf.CHECK_GL_ERROR()
end

function trihex_scene.initGL()
    vbos = {}
    local vaoId = ffi.new("int[1]")
    gl.GenVertexArrays(1, vaoId)
    vao = vaoId[0]
    gl.BindVertexArray(vao)
    init_shader()
    init_flake_attributes()
    gl.BindVertexArray(0)
end

function trihex_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)
    gl.PolygonMode(GL.FRONT_AND_BACK, GL.FILL)
end

local function draw_single_flake()
    gl.BindVertexArray(vao)
    gl.DrawElements(GL.TRIANGLES, 3*12, GL.UNSIGNED_INT, nil)
    gl.BindVertexArray(0)
end

function trihex_scene.render_for_one_eye(mview, proj)
    gl.UseProgram(prog)
    gl.UniformMatrix4fv(umv_loc, 1, GL.FALSE, glFloatv(16, mview))
    gl.UniformMatrix4fv(upr_loc, 1, GL.FALSE, glFloatv(16, proj))
    gl.Uniform2f(jp_loc, jparmx, jparmy)
    gl.Uniform2f(jo_loc, joffx, joffy)
    gl.Uniform2f(js_loc, jscalex, jscaley)

    gl.Enable(GL.BLEND)
    gl.BlendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA)
    draw_single_flake()
    gl.Disable(GL.BLEND)
    sf.CHECK_GL_ERROR()

    gl.UseProgram(0)
end

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

return trihex_scene
