-- twopassblur_effect.lua
twopassblur_effect = {}

local openGL = require("opengl")
local ffi = require("ffi")
local sf = require("util.shaderfunctions")
local fbf = require("util.fbofunctions")

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 blur_prog = 0
local vbos = {}

local basic_vert = [[
#version 410 core

in vec4 vPosition;
in vec4 vColor;
out vec3 vfTexCoord;

void main()
{
    vfTexCoord = vec3(.5*(vColor.xy+1.),0.);
    gl_Position = vec4(vPosition.xy, 0., 1.);
}
]]

local blur_frag = [[
#version 330

in vec3 vfTexCoord;
out vec4 fragColor;

uniform sampler2D tex;
uniform vec2 step;

float offset[3] = float[]( 0., 1., 2.);
float weight[3] = float[]( .5, .3, .1);
//float offset[3] = float[]( 0.0, 1.38, 3.23);
//float weight[3] = float[]( 0.227, 0.316, 0.070);
//float offset[8] = float[]( 0., 1., 2., 3., 4., 5., 6., 7.);
//float weight[5] = float[]( 0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162 );
//float weight[4] = float[]( .415, .262, .048, .003);
//float weight[8] = float[](0.159576912161, 0.147308056121, 0.115876621105, 0.0776744219933, 0.0443683338718, 0.0215963866053, 0.00895781211794, 0.0044299121055113265);

void main()
{
    vec4 sum = vec4(0.);
    sum += texture(tex, vfTexCoord.xy) * weight[0];
    for( int i=1; i<3; i++ )
    {
        sum += texture(tex, vfTexCoord.xy + step * offset[i]) * weight[i];
        sum += texture(tex, vfTexCoord.xy - step * offset[i]) * weight[i];
    }
    sum.a = 1.;
    fragColor = sum;
}
]]

local function init_quad_attributes(prog)
    local verts = glFloatv(4*2, {
        -1,-1,
        1,-1,
        1,1,
        -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, 2, 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.DYNAMIC_DRAW)
    sf.CHECK_GL_ERROR()
    gl.VertexAttribPointer(vcol_loc, 2, 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*2, {
        1,0,2,
        2,0,3,
    })
    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 twopassblur_effect.initGL()
    vbos = {}
    texs = {}
    local vaoId = ffi.new("int[1]")
    gl.GenVertexArrays(1, vaoId)
    vao = vaoId[0]
    gl.BindVertexArray(vao)

    blur_prog = sf.make_shader_from_source({
        vsrc = basic_vert,
        fsrc = blur_frag,
        })

    init_quad_attributes(blur_prog)
    gl.BindVertexArray(0)
end

function twopassblur_effect.exitGL()
    for k,v in pairs(vbos) do
        gl.DeleteBuffers(1,v)
    end
    vbos = nil
    gl.DeleteProgram(blur_prog)

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

    fbf.deallocate_fbo(twopassblur_effect.fbo)
    fbf.deallocate_fbo(twopassblur_effect.fbo2)
end

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

function twopassblur_effect.bind_fbo()
    local f = twopassblur_effect.fbo
    if f then
        fbf.bind_fbo(f)
        gl.Viewport(0, 0, f.w, f.h)
    end
end

function twopassblur_effect.unbind_fbo()
    fbf.unbind_fbo()
end

function twopassblur_effect.resize_fbo(w,h)
    local e = twopassblur_effect
    if e.fbo then fbf.deallocate_fbo(e.fbo) end
    if e.fbo2 then fbf.deallocate_fbo(e.fbo2) end
    e.fbo = fbf.allocate_fbo(w/2,h/2)
    e.fbo2 = fbf.allocate_fbo(w/2,h/2)
end

-- Module-internal function for first blur pass.
function do_first_pass(texId, resx, resy)
    if twopassblur_effect.fbo2 then
        fbf.bind_fbo(twopassblur_effect.fbo2)

        local prog = blur_prog
        gl.UseProgram(prog)
        gl.ActiveTexture(GL.TEXTURE0)
        gl.BindTexture(GL.TEXTURE_2D, texId)
        local tx_loc = gl.GetUniformLocation(prog, "tex")
        gl.Uniform1i(tx_loc, 0)
        local step_loc = gl.GetUniformLocation(prog, "step")
        gl.Uniform2f(step_loc, 1/resx, 0)
        draw_fullscreen_quad()
        sf.CHECK_GL_ERROR()
        gl.UseProgram(0)

        fbf.unbind_fbo()
    end
end

-- Module-internal function to write "immediate" buffer to back first-pass buffer,
-- then present the results of that to framebuffer via the second-pass shader.
function twopassblur_effect.present_texture(texId, resx, resy, winw, winh)
    do_first_pass(texId, resx, resy)

    if winw and winh then
        gl.Viewport(0, 0, winw, winh)
    end

    -- Then present fbo2's tex to back buffer, applying vert blur
    local prog = blur_prog
    gl.UseProgram(prog)
    gl.ActiveTexture(GL.TEXTURE0)
    gl.BindTexture(GL.TEXTURE_2D, twopassblur_effect.fbo2.tex)
    local tx_loc = gl.GetUniformLocation(prog, "tex")
    gl.Uniform1i(tx_loc, 0)
    local step_loc = gl.GetUniformLocation(prog, "step")
    gl.Uniform2f(step_loc, 0, 1/resy)
    draw_fullscreen_quad()
    sf.CHECK_GL_ERROR()
    gl.UseProgram(0)
end

function twopassblur_effect.present(win_w, win_h)
    local f = twopassblur_effect.fbo
    gl.Viewport(0,0, f.w, f.h)
    twopassblur_effect.present_texture(f.tex, f.w, f.h, win_w, win_h)
end

return twopassblur_effect
