-- jellyfish_head.lua
jellyfish_head = {}

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 vao = 0
local prog = 0
local num_tri_idxs = 0
local osc = 0
local vbos = {}

local basic_vert = [[
#version 330

in vec4 vPosition;

out vec3 vfColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;
uniform float u_osc;
uniform float u_ampl;
uniform float u_freq;

vec2 cartesianToPolar( in vec2 pt )
{
    float r = 2.5 * length( pt );
    float a = atan( pt.y, pt.x );
    return vec2( r, a );
}

void main()
{
    vec3 centerPt = vec3(0.);
    vec3 relPos = vPosition.xyz - centerPt;
    vec3 up = vec3(1.,0.,0.);
    float x = u_freq*dot(relPos, up) + u_osc;

    vec4 norm = vec4(relPos, 0.) * (1.0 + .05 * cos(x));
    vfColor = norm.xyz;
    relPos *= 1.0 + u_ampl * sin(x);

    vec2 polar = cartesianToPolar(relPos.yz);
    float dotup = abs(dot(norm.xyz, up));
    relPos *= 1. + (1.-dotup)*.3 * pow(abs(sin(4.*polar.y)), 8.);

    gl_Position = prmtx * mvmtx * vec4(relPos.yxz, 1.);
}
]]

local basic_frag = [[
#version 330

in vec3 vfColor;
out vec4 fragColor;

uniform float u_fogDist;

void main()
{
    float dist = gl_FragCoord.z / gl_FragCoord.w;
    float m = 1.-exp(-dist/u_fogDist);
    vec3 fogCol = vec3(0.);
    float colBoost = 1.3;
    fragColor = vec4(mix(colBoost*vfColor, fogCol, m), 1.);
}
]]

function init_cube_attributes()
    local facets = 15

    local v = {}
    function make_points(mtx)
        for j=0,facets do
            for i=0,facets do
                pt = {i/facets,j/facets,0,1} -- xywz
                pt =mm.transform(pt, mtx)
                table.insert(v, pt[1])
                table.insert(v, pt[2])
                table.insert(v, pt[3])
            end
        end
    end

    local f = {}
    function make_faces(first)
        for j=1,facets do
            for i=1,facets do
                xy = {i,j}
                local function getidx(i,j)
                    return (facets+1)*j + i + first
                end
                table.insert(f, getidx(i,j))
                table.insert(f, getidx(i,j-1))
                table.insert(f, getidx(i-1,j-1))

                table.insert(f, getidx(i,j))
                table.insert(f, getidx(i-1,j-1))
                table.insert(f, getidx(i-1,j))
            end
        end
    end

    local mtx = {
        1,0,0,0,
        0,1,0,0,
        0,0,1,0,
        0,0,0,1,
    }
    local mtx2 = {
        1,0,0,0,
        0,0,1,0,
        0,-1,0,0,
        0,0,0,1,
    }
    local mtx3 = {
        0,1,0,0,
        0,0,1,0,
        -1,0,0,0,
        0,0,0,1,
    }
    local mtx4 = {
        1,0,0,0,
        0,1,0,0,
        0,0,1,0,
        0,0,1,1,
    }
    local mtx5 = {
        1,0,0,0,
        0,0,1,0,
        0,-1,0,0,
        0,1,0,1,
    }
    local mtx6 = {
        0,1,0,0,
        0,0,1,0,
        -1,0,0,0,
        1,0,0,1,
    }
    local matrices = {mtx, mtx2, --[[mtx3,]] mtx4, mtx5, mtx6}

    local fs = (facets+1)*(facets+1)
    for k,v in pairs(matrices) do
        make_faces((k-1)*fs)
        make_points(v)
    end

    -- Center all points on origin(generated in [0,1])
    for i=1,#v do
        v[i] = v[i] - 0.5
    end

    -- deform into sphere
    local centerpt = {0,0,0}
    local function vector_subtract(a, b)
        return {a[1]-b[1], a[2]-b[2], a[3]-b[3]}
    end

    for i=1,#v,3 do
        local x = {v[i], v[i+1], v[i+2]}
        local txp = vector_subtract(x,centerpt)
        mm.normalize(txp)
        -- Try to turn lower half into a cylinder
        if txp[1] < 0 then
            local planar = {0, txp[2], txp[3]}
            mm.normalize(planar)
            planar[1] = x[1]
            txp[1] = planar[1]
            txp[2] = planar[2]
            txp[3] = planar[3]
        end
        v[i] = txp[1]
        v[i+1] = txp[2]
        v[i+2] = txp[3]
    end


    num_tri_idxs = #f
    local verts = glFloatv(#v, v)
    local quads = glUintv(#f, f)

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

    gl.EnableVertexAttribArray(vpos_loc)

    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 jellyfish_head.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,
        })

    init_cube_attributes()
    gl.BindVertexArray(0)
end

function jellyfish_head.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

function draw_color_cube()
    gl.BindVertexArray(vao)
    gl.DrawElements(GL.TRIANGLES, num_tri_idxs, GL.UNSIGNED_INT, nil)
    gl.BindVertexArray(0)
end

function jellyfish_head.render_for_one_eye(mview, proj, e)
    local umv_loc = gl.GetUniformLocation(prog, "mvmtx")
    local upr_loc = gl.GetUniformLocation(prog, "prmtx")
    local uosc_loc = gl.GetUniformLocation(prog, "u_osc")
    local uamp_loc = gl.GetUniformLocation(prog, "u_ampl")
    local ufrq_loc = gl.GetUniformLocation(prog, "u_freq")
    local ufgd_loc = gl.GetUniformLocation(prog, "u_fogDist")

    gl.UseProgram(prog)
    gl.UniformMatrix4fv(umv_loc, 1, GL.FALSE, glFloatv(16, mview))
    gl.UniformMatrix4fv(upr_loc, 1, GL.FALSE, glFloatv(16, proj))
    gl.Uniform1f(uosc_loc, osc)
    gl.Uniform1f(uamp_loc, e.headAmpl)
    gl.Uniform1f(ufrq_loc, e.headFreq)
    gl.Uniform1f(ufgd_loc, jellyfish_head.fogDist)
    draw_color_cube()
    gl.UseProgram(0)
end

function jellyfish_head.timestep(absTime, dt)
    osc = 10.*absTime
end

return jellyfish_head
