-- jellyfish_tentacles.lua
jellyfish_tentacles = {}

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]')

jellyfish_tentacles.vao = 0
jellyfish_tentacles.prog_spline = 0
local vbos = {}


local spline_vert = [[
#version 330
#line 44

in vec4 vPosition;
in vec4 vColor;

out vec3 vfColor;

uniform mat4 mvmtx;
uniform mat4 prmtx;

uniform int num_cubes;
uniform vec3 spline_points[4];
uniform float wave_phase;
uniform float wave_ampl;
uniform float tent_thick;
uniform float tent_len;

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
            );
}

vec3 GetUpVectorAt(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t)
{
    // Find local tangent
    float eps = .05;
    vec3 pt00 = GetCatmullRomPoint(p0, p1, p2, p3, t);
    vec3 pt01 = GetCatmullRomPoint(p0, p1, p2, p3, t+eps);
    vec3 a = normalize(pt01 - pt00);

    // Guess a bitangent
    vec3 x = vec3(1.,0.,0.);
    vec3 y = vec3(0.,1.,0.);
    vec3 guess = x;
    float small = .05;
    //if (dot(a,guess) < small)
    //    guess = y;
    vec3 cp0 = normalize(cross(a, guess));
    return cross(cp0, a);
}

void main()
{
    vfColor = vColor.xyz;
    vec3 pos3 = vPosition.xyz;

    float t = float(gl_InstanceID) / float(num_cubes);
    t = t*t;

    vec3 L = tent_len * vec3(1.,1.,-1.);
    vec3 p0 = spline_points[0].yxz * L;
    vec3 p1 = spline_points[1].yxz * L;
    vec3 p2 = spline_points[2].yxz * L;
    vec3 p3 = spline_points[3].yxz * L;

    vec3 sp = GetCatmullRomPoint(p0, p1, p2, p3, t);

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

    // wave action
    float ampl = wave_ampl;
    float freq = 32.;
    pos3 +=
        //GetUpVectorAt(p0, p1, p2, p3, t) *
        smoothstep(0., .2, (1.-t)) * // t=1 at head
        ampl*sin(freq*(t+0.3*wave_phase));

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

local spline_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.);
}
]]

local function init_cube_attributes()
    local v = {
        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 cols = glFloatv(3*8, v)
    -- Center geometry around origin
    for i=1,#v do
        v[i] = v[i] - 0.5
        v[i] = v[i] * 0.5
    end
    local verts = glFloatv(3*8, v)

    local vpos_loc = gl.GetAttribLocation(jellyfish_tentacles.prog_spline, "vPosition")
    local vcol_loc = gl.GetAttribLocation(jellyfish_tentacles.prog_spline, "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(cols), cols, 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)

    sf.CHECK_GL_ERROR()
end

function jellyfish_tentacles.initGL()
    vbos = {}

    local vaoId = ffi.new("int[1]")
    gl.GenVertexArrays(1, vaoId)
    jellyfish_tentacles.vao = vaoId[0]
    gl.BindVertexArray(jellyfish_tentacles.vao)
    jellyfish_tentacles.prog_spline = sf.make_shader_from_source({
        vsrc = spline_vert,
        fsrc = spline_frag,
        })
    init_cube_attributes()
    gl.BindVertexArray(0)
end

function jellyfish_tentacles.exitGL()
    gl.BindVertexArray(jellyfish_tentacles.vao)
    for _,v in pairs(vbos) do
        gl.DeleteBuffers(1,v)
    end

    vbos = {}
    gl.DeleteProgram(jellyfish_tentacles.prog_spline)

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

function jellyfish_tentacles.draw_color_cube_row(spl_pts, e)
    local prog = jellyfish_tentacles.prog_spline
    local nc_loc = gl.GetUniformLocation(prog, "num_cubes")
    gl.Uniform1i(nc_loc, e.num_cubes)
    local sp_loc = gl.GetUniformLocation(prog, "spline_points")
    gl.Uniform3fv(sp_loc, 4, spl_pts)
    local wp_loc = gl.GetUniformLocation(prog, "wave_phase")
    gl.Uniform1f(wp_loc, e.wave_phase)
    local wa_loc = gl.GetUniformLocation(prog, "wave_ampl")
    gl.Uniform1f(wa_loc, e.wave_ampl)
    local tt_loc = gl.GetUniformLocation(prog, "tent_thick")
    gl.Uniform1f(tt_loc, e.tentThick)
    local tl_loc = gl.GetUniformLocation(prog, "tent_len")
    gl.Uniform1f(tl_loc, e.tentLen)
    local ufgd_loc = gl.GetUniformLocation(prog, "u_fogDist")
    gl.Uniform1f(ufgd_loc, jellyfish_tentacles.fogDist)

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


return jellyfish_tentacles
