--[[
    graphics.lua

    A top-level container to wrap all graphics operations and expose
    the typical init-display-timestep-resize-exit API.
]]

local mm = require("util.matrixmath")

local graphics = {}

local win_w = 800
local win_h = 600

local Scene = require("scene.jellyfish_scene")
local ShadertoyAtPartyScene = require("scene.shadertoy_atparty")
local Cameras = {
    require("camera.lookat_camera"),
    require("camera.lookahead_camera"),
    require("camera.rotate_camera"),
}
local PostFxs = {
    require("effect.onepassblur_effect"),
    require("effect.twopassblur_effect"),
    require("effect.compound_effect"),
    require("effect.iir_effect"),
    require("effect.chain_effect"),
}
local Camera = Cameras[2]
local PostFx = nil

local animParams = {
    drawlist = 3
}

-- Some initial conditions
Scene.BPM = 124.0050492
graphics.draw_shadertoy = false
graphics.draw_overlay_text = false

local g_lastFilterSwapTime = 0

function graphics.switch_to_scene(name)
    if Scene and Scene.exitGL then
        Scene.exitGL()
    end
    package.loaded[name] = nil
    Scene = nil

    if not Scene then
        Scene = require(name)
        if Scene then
            Scene.initGL()
        end
    end
end

function graphics.switch_to_effect(i)
    PostFx = PostFxs[i]
end

function graphics.switch_to_camera(i)
    Camera = Cameras[i] 
end

function graphics.switch_draw_list()
    local d = animParams.drawlist
    d = d + 1
    if d>4 then d=1 end
    animParams.drawlist = d
end

-- This is an ugly hackaround to set initial conditions to match tracker.
function graphics.reset_camera_to_initial()
    Cameras[2].params.loc[3] = -80
end

function graphics.cammove(v)
    Camera.cammove(v)
end

function graphics.camyaw(v)
    Camera.camyaw(v)
end

function graphics.targetmove(v)
    Camera.targetmove(v)
end

function graphics.get_pts(i)
    ptss = {
        Scene.global_manual,
        Scene.global_manual_arms,
    }
    return ptss[i]
end

function graphics.toggle_animation()
   if Scene.animate ~= nil then
       Scene.animate = not Scene.animate
   end
end

function graphics.setbpm(bpm)
    Scene.BPM = bpm
end

function graphics.dump_info()
    -- Dump manual spline points
    local s = Scene
    if s.global_manual then
        if s.print_spline_pts then
            s.print_spline_pts(s.global_manual)
            s.print_spline_pts(s.global_manual_arms)
        end
    end
    -- Dump camera params
    if Camera then
        local inspect = require("util.inspect")
        print(inspect.inspect(Camera.params))
    end
end

function graphics.display()
    -- Render scenes to texture for post processing
    if PostFx then PostFx.bind_fbo() end
    gl.ClearColor(0,0,0,0)
    gl.Clear(GL.COLOR_BUFFER_BIT + GL.DEPTH_BUFFER_BIT)
    gl.Enable(GL.DEPTH_TEST)

    if Scene then
        local m = Camera.get_matrix()
        local p = {}
        mm.glh_scale(m, 1,-1,-1)
        mm.glh_perspective(p, 75.0, win_w/win_h, 0.004, 500.0)
        Scene.render_for_one_eye(m, p, animParams)
        --[[
        -- Set up window coords
        local id = {}
        mm.make_identity_matrix(id)
        local orth = {}
        mm.glh_ortho(orth, 0, win_w, win_h, 0, -1, 1)
        Scene.render_for_one_eye(id, orth)
        ]]
    end

    gl.Disable(GL.DEPTH_TEST)

    -- Intro/outro shader display - uses discard for show-through
    if graphics.draw_shadertoy and ShadertoyAtPartyScene then
        ShadertoyAtPartyScene.render_for_one_eye()
    end

    if PostFx then PostFx.unbind_fbo() end

    -- Apply post-processing and present
    if PostFx then
        gl.Disable(GL.DEPTH_TEST)
        gl.Viewport(0,0, win_w, win_h)
        PostFx.present(win_w, win_h)

        -- Swap IIR buffers at timed intervals
        if PostFx.swap then
            -- This makes the IIR filter look much more interesting
            local filterSwapInterval = 1/60
            if (os.clock() - g_lastFilterSwapTime) > filterSwapInterval then
                g_lastFilterSwapTime = os.clock()
                PostFx.swap()
            end
        end
    end

    -- Draw a text overlay
    if graphics.draw_overlay_text then
        local m = {}
        local p = {}
        mm.make_identity_matrix(m)
        -- Try here to make a matrix that undoes the odd adjustments made in shader.
        -- This is not quite right, but perhaps good enough.
        mm.glh_translate(m, 3, -2, 0)
        local ds = 30
        mm.glh_ortho(p, 0, win_w/ds, 0, win_h/ds, -1, 1)
        local linew = 1.8
        local strings = {
            "oh2.mp3",
            "CapsAdmin",
            "finished",
            "soundcloud.com/capsadmin",
        }
        for i = #strings, 1, -1 do
            s = strings[i]
            draw_text_string(m, p, s)
            mm.glh_translate(m, 0, linew, 0)
        end
    end
end

function graphics.resize(w, h)
    win_w, win_h = w, h
    if PostFx then PostFx.resize_fbo(w,h) end
    if Scene then
        if Scene.resize_window then Scene.resize_window(w, h) end
    end
    if ShadertoyAtPartyScene then
        ShadertoyAtPartyScene.resize_window(win_w, win_h)
    end

    for k=1,#PostFxs do
        local p = PostFxs[k]
        p.resize_fbo(win_w,win_h)
    end
end

function graphics.init_gl()
    if Scene then Scene.initGL() end
    if ShadertoyAtPartyScene then ShadertoyAtPartyScene.initGL() end

    for k=1,#PostFxs do
        local p = PostFxs[k]
        p.initGL()
        p.resize_fbo(win_w,win_h)
    end
end

function graphics.timestep(absTime, dt)
    --animParams.tparam = math.abs(math.sin(absTime))
    if Scene then Scene.timestep(absTime, dt) end
    if ShadertoyAtPartyScene then ShadertoyAtPartyScene.timestep(absTime, dt) end
end

graphics.sync_callbacks = {
    ["camnum"] = function(c)
        graphics.switch_to_camera(c)
    end,
    ["posteffect_idx"] = function(t)
        if t then graphics.switch_to_effect(t) end
    end,
    ["posteffect.iirmix"] = function(t)
        if t then PostFxs[4].mix_coeff = t end
    end,
    ["drawlist"] = function(d)
        if d then animParams.drawlist = d end
    end,
    ["cam2_locx"] = function(x) if x then Cameras[2].params.loc[1] = x end end,
    ["cam2_locy"] = function(x) if x then Cameras[2].params.loc[2] = x end end,
    ["cam2_locz"] = function(x) if x then Cameras[2].params.loc[3] = x end end,

    ["cam3_theta"] = function(x) if x then Cameras[3].params.theta = x end end,
    ["cam3_radius"] = function(x) if x then Cameras[3].params.radius = x end end,
    ["cam3_height"] = function(x) if x then Cameras[3].params.height = x end end,

    ["show_at_shader"] = function(sa) if sa then graphics.draw_shadertoy = (sa == 1) end end,
    ["atshader_fov"] = function(sf) if sf then ShadertoyAtPartyScene.fov_scale = sf end end,
    ["atshader_cup"] = function(sc) if sc then ShadertoyAtPartyScene.cube_up = sc end end,
    ["atshader_pon"] = function(po) if po then ShadertoyAtPartyScene.party_on = po end end,

    ["conga_t"] = function(t)
        if t then Scene.conga_t = t end
    end,
    ["random_t"] = function(t)
        if t then Scene.random_t = t end
    end,
    ["standard_kfs_idx"] = function(t)
        if t then Scene.standard_kfs_idx = t end
    end,
    ["chorusline_kfs_idx"] = function(t)
        if t then Scene.chorusline_kfs_idx = t end
    end,
    ["do_random_ikfs"] = function(x)
        if x then Scene.do_random_individual_kfs = (x==1) end
    end,
    ["draw_text_labels"] = function(x)
        if x then Scene.draw_text_labels = (x==1) end
    end,
    ["graphics.draw_overlay_text"] = function(x)
        if x then graphics.draw_overlay_text = (x==1) end
    end,

    ["banner.draw"] = function(x)
        if x then Scene.draw_banner_holders = (x==1) end
    end,
    ["banner1_x"] = function(x) if x then Scene.banner1[1] = x end end,
    ["banner1_y"] = function(x) if x then Scene.banner1[2] = x end end,
    ["banner1_z"] = function(x) if x then Scene.banner1[3] = x end end,
    ["banner1_text_idx"] = function(x) if x then Scene.banner_msg_idx = x end end,
}

function graphics.sync_params(get_cur_param)
    -- The get_cur_param function is passed in from the calling main func
    local f = get_cur_param
    if not f then return end

    for k,_ in pairs(graphics.sync_callbacks) do
        local g = graphics.sync_callbacks[k]
        local val = f(k)
        if g then g(val) end
    end
end

return graphics
