local demo_dir = gh_utils.get_demo_dir()
local lib_dir = gh_utils.get_lib_dir() 		
dofile(lib_dir .. "lua/gxl_stdlib.lua")    
dofile(lib_dir .. "lua/keyboard_codes.lua")   




function LOG(what)
  gh_utils.trace("DEMO: " .. what)
end
    
function GL_ERR(where)    
  --[[
  local GL_INVALID_ENUM = 1280 -- 0x0500
  local GL_INVALID_VALUE = 1281 -- 0x0501
  local GL_INVALID_OPERATION = 1282 -- 0x0502
  local GL_STACK_OVERFLOW = 1283 -- 0x0503
  local GL_STACK_UNDERFLOW = 1284 -- 0x0504
  local GL_OUT_OF_MEMORY = 1285 -- 0x0505
  --]]
  local glerr = gh_renderer.get_opengl_error()
  if (glerr >= 0) then
    local code_str = "GL_OK"
    if (glerr == 1280) then
      code_str = "GL_INVALID_ENUM"
    elseif (glerr == 1281) then
      code_str = "GL_INVALID_VALUE"
    elseif (glerr == 1282) then
      code_str = "GL_INVALID_OPERATION"
    elseif (glerr == 1283) then
      code_str = "GL_STACK_OVERFLOW"
    elseif (glerr == 1284) then
      code_str = "GL_STACK_UNDERFLOW"
    elseif (glerr == 1285) then
      code_str = "GL_OUT_OF_MEMORY"
    end
    gh_utils.trace("GL error " .. where .. " - code = " .. code_str)
  end
end

function LogOpenGLCap_1i(cap_name)    
  local cap_value = gh_renderer.get_capability_4i(cap_name)
  gh_utils.trace(cap_name .. " = " .. cap_value)
end  

function LogOpenGLCap_3i(cap_name)    
  local x, y, z = gh_renderer.get_capability_4i(cap_name)
  gh_utils.trace(string.format("%s = %d / %d / %d", cap_name, x, y, z))
end  

function RandomInit()
  math.randomseed(1234)
end

function Random(a, b)
	if (a > b) then
		local c = b
		b = a
		a = c
	end
	local delta = b-a
	return (a + math.random()*delta)
end

function rand()
  return math.random()
end
    


winW, winH = gh_window.getsize(0)
    
   

-- A perspective camera for the 3D scene.
local aspect = 1.333
if (winH > 0) then
  aspect = winW / winH
end  
camera = gh_camera.create_persp(60, aspect, 0.1, 1000.0)
gh_camera.set_viewport(camera, 0, 0, winW, winH)
gh_camera.set_position(camera, 0, 10, 30)
gh_camera.set_lookat(camera, 0, 0, 0, 1)
gh_camera.setupvec(camera, 0, 1, 0, 0)




camera_ortho = gh_camera.create_ortho(-winW/2, winW/2, -winH/2, winH/2, 1.0, 10.0)
gh_camera.set_viewport(camera_ortho, 0, 0, winW, winH)
gh_camera.set_position(camera_ortho, 0, 0, 4)

  

particle_prog = gh_node.getid("particle_prog")
gh_gpu_program.uniform1i(particle_prog, "tex0", 0)




RandomInit()



-- The vertex pool --------------------------------
--
particle_count = 8192 * 32
vp = gh_vertex_pool.create(particle_count)
-- Init particles position and color
for i=0, particle_count-1 do
  --x = Random(-10.0, 10.0)
  --y = Random(-10.0, 10.0)
  --z = Random(-10.0, 10.0)
  --gh_vertex_pool.vertex_set_position(vp, i, x, y, z, 1)
  gh_vertex_pool.vertex_set_color(vp, i, 1, 1, 1, 1)
end

-- GL_ERR("0")        

  -- Compute shader.
  compute_prog = 0

ssbo_positions = 0
ssbo_velocities = 0
ssbo_attractors = 0
num_attractors = 1
attractors_masses = {}
attractors_positions = {}


attractor_pos = {x=450, y=200}


function ResetParticles()

  -- Position buffer -----------------
  --
  if (ssbo_positions > 0) then

    local particle_size_bytes = gh_utils.get_vertex_size() -- A vertex has four vec4 attribs, a vec4 is 16 byte-lenght.
    local storage_size = particle_count * particle_size_bytes

    gh_gpu_buffer.bind(ssbo_positions)
    
    -- Map the position buffer and fill it.
    --
    gh_gpu_buffer.map_range(ssbo_positions, 0, storage_size, "GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT")
    local buffer_offset_bytes = 0
    local cursorX = winW / 2.0
    local cursorY = winH / 2.0
    local destPosX = ((cursorX / winW)-0.5) * 2.0
    local destPosY = ((winH - cursorY) / winH - 0.5) * 2.0

    local part_color_r = Random(0.5, 1.0)
    local part_color_g = Random(0.5, 1.0)
    local part_color_b = Random(0.5, 1.0)

    for i=0, particle_count-1 do
      local rnd = rand();
      local rndVal = rand() * (360.0 * 3.14 * 2.0)
      local rndRad = rand() * winH/2.0 -- TODO : Change multiplier to get cool effects (e.g. wider range)

      local x = destPosX + math.cos(rndVal) * rndRad
      local y = destPosY + math.sin(rndVal) * rndRad
      local z = 0
      local w = Random(0.0, 1.0) -- particle lifetime
      local position_offset = buffer_offset_bytes
      gh_gpu_buffer.set_value_4f(ssbo_positions, position_offset, x, y, z, w)
      
      local color_offset = buffer_offset_bytes + (16*3) -- 3: P / UV0 / N / C
      gh_gpu_buffer.set_value_4f(ssbo_positions, color_offset, part_color_r, part_color_g, part_color_b, 1)
      
      -- Next particle!
      buffer_offset_bytes = buffer_offset_bytes + particle_size_bytes
    end

    gh_gpu_buffer.unmap(ssbo_positions)
    gh_gpu_buffer.unbind(ssbo_positions)
    LOG("init ssbo_positions OK.")
  end


  -- Velocity buffer -----------------
  --
  if (ssbo_velocities > 0) then

    local velocity_size_bytes = 16 -- vec4 = 16 bytes
    storage_size = particle_count * velocity_size_bytes

    gh_gpu_buffer.bind(ssbo_velocities)
    
    -- Map the position buffer and fill it with zeros.
    --
    gh_gpu_buffer.map_range(ssbo_velocities, 0, storage_size, "GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT")
    local buffer_offset_bytes = 0
    for i=0, particle_count-1 do
      local x = 0
      local y = 0
      local z = 0
      local w = 1
      local position_offset = buffer_offset_bytes
      gh_gpu_buffer.set_value_4f(ssbo_velocities, position_offset, x, y, z, w)
      
      -- Next velocity!
      buffer_offset_bytes = buffer_offset_bytes + velocity_size_bytes
    end
    gh_gpu_buffer.unmap(ssbo_velocities)
    gh_gpu_buffer.unbind(ssbo_velocities)
    LOG("init ssbo_velocities OK.")
  end

end



GL_ARB_compute_shader_ok = gh_renderer.check_opengl_extension("GL_ARB_compute_shader")
GL_ARB_compute_variable_group_size_ok = gh_renderer.check_opengl_extension("GL_ARB_compute_variable_group_size")

if (GL_ARB_compute_shader_ok == 1) then

  LOG("init ssbo_positions...")
  -- Shader storage buffer (SSBO).
  --
  local particle_size_bytes = gh_utils.get_vertex_size() -- A vertex has four vec4 attribs, a vec4 is 16 byte-lenght.
  local storage_size = particle_count * particle_size_bytes
  gh_utils.trace("storage_size = " .. storage_size)
  local flags = ""
  ssbo_positions = gh_gpu_buffer.create("SHADER_STORAGE", "GL_DYNAMIC_COPY", storage_size, flags)
  
  LOG("init ssbo_velocities...")
  local velocity_size_bytes = 16 -- vec4 = 16 bytes
  storage_size = particle_count * velocity_size_bytes
  ssbo_velocities = gh_gpu_buffer.create("SHADER_STORAGE", "GL_DYNAMIC_COPY", storage_size, flags)



  ResetParticles()


        
  -- Tells GeeXLab that the Vertex Pool has to use the positions SSBO as vertex source
  --
  gh_vertex_pool.set_vertex_source(vp, ssbo_positions)
    
        



  if (GL_ARB_compute_variable_group_size_ok == 1) then
    compute_prog = gh_gpu_program.create_from_shader_files("compute_prog", "", "", "", "", "", demo_dir .. "shaders/compute_var_group_size.glsl")
  else
    compute_prog = gh_gpu_program.create_from_shader_files("compute_prog", "", "", "", "", "", demo_dir .. "shaders/compute.glsl")
  end
  --LOG("init compute_prog OK.")


  gh_gpu_program.uniform1f(compute_prog, "deltaT", 0)
  -- Viewport dimensions for border clamp
  gh_gpu_program.uniform2f(compute_prog, "vpDim", winW, winH)
  gh_gpu_program.uniform1i(compute_prog, "borderClamp", 1)

  gh_gpu_program.uniform3f(compute_prog, "destPos", attractor_pos.x, attractor_pos.y, 0)
  
 


  
  -- Set block binding points.
  --
  local binding_point_index = 0 
  --local block_index = gh_gpu_program.get_interface_block_index(compute_prog, "SHADER_STORAGE", "position_buffer")
  --gh_gpu_program.set_shader_storage_block_binding(compute_prog, block_index, binding_point_index)
  gh_gpu_buffer.bind_base(ssbo_positions, binding_point_index)
  
  binding_point_index = 1 
  gh_gpu_buffer.bind_base(ssbo_velocities, binding_point_index)
  
  
  LOG("init SSBO binding point indices OK.")
end



gh_utils.trace("================================================")
gh_utils.trace("Shader storage buffer implementation limits:")
LogOpenGLCap_1i("GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT")
LogOpenGLCap_1i("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS")
LogOpenGLCap_1i("GL_MAX_SHADER_STORAGE_BLOCK_SIZE")
LogOpenGLCap_1i("GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_1i("GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_1i("GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_1i("GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_1i("GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_1i("GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS")

gh_utils.trace("================================================")
gh_utils.trace("Compute Shaders implementation limits:")
LogOpenGLCap_3i("GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS")
LogOpenGLCap_3i("GL_MAX_COMPUTE_WORK_GROUP_COUNT")
LogOpenGLCap_3i("GL_MAX_COMPUTE_WORK_GROUP_SIZE")
LogOpenGLCap_3i("GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS")
LogOpenGLCap_3i("GL_MAX_COMPUTE_SHARED_MEMORY_SIZE")
gh_utils.trace("================================================")





local abs_path = 0
tex01 = gh_texture.create_from_file("textures/2201-v2.jpg", 0, abs_path)


--light_sphere = gh_utils.sphere_create(10.0, 10, 1, 1, 1, 1)
attractor_disc = gh_utils.circle_create_v2(1, 10.0, 20, 1, 1, 1, 1)
color_prog = gh_node.getid("color_prog")
gh_gpu_program.uniform4f(color_prog, "color", 1, 1, 1, 1)




--gh_utils.twbar_resize(winW, winH)
--bar1 = gh_utils.twbar_create("bar1")
--gh_utils.twbar_define(" bar1 size='250 250' label='Particle Painter' movable=true resizable=true position='10 80' color='35 65 80' alpha='130' text='light' fontsize=2 contained=true ")

pencil_color = {r=1.0, g=0.0, b=0.0, a=1.0}
--gh_utils.twbar_add_var_rgba(bar1, "pencil_color", " label='Pencil color'")

pencil_size = 3
--gh_utils.twbar_add_var_float(bar1, "pencil_size", " label='Pencil size' min=0.1 max=10 step=0.1")

--gh_utils.twbar_update()
--gh_utils.twbar_draw()





gh_renderer.set_scissor_state(1)



function blending_factor_to_str(f)
  if (f == 0) then
    return "ZERO"
  elseif (f == 1) then
    return "ONE"
  elseif (f == 2) then
    return "SRC_ALPHA"
  elseif (f == 3) then
    return "ONE_MINUS_DST_ALPHA"
  elseif (f == 4) then
    return "ONE_MINUS_DST_COLOR"
  elseif (f == 5) then
    return "ONE_MINUS_SRC_ALPHA"
  elseif (f == 6) then
    return "DST_COLOR"
  elseif (f == 7) then
    return "DST_ALPHA"
  elseif (f == 8) then
    return "SRC_COLOR"
  elseif (f == 9) then
    return "ONE_MINUS_SRC_COLOR"
  end
  return "NONE"
end



gh_renderer.set_vsync(0)

last_time = gh_utils.get_elapsed_time()

gl_renderer = gh_renderer.get_renderer_model()
gl_version = gh_renderer.get_api_version()

fps_time = 0
fps = 0
frames = 0




attractor_auto_move = 0
attractor_auto_move_reset_time = 10.0
attractor_auto_move_time = 0.0

host_app_name = ""

if _G['gh_utils']['get_app_name'] ~= nil then 
  host_app_name = gh_utils.get_app_name()
end

if (host_app_name == "GPU Caps Viewer") then
  attractor_auto_move = 1
end  

attractor_radius_x = 40
attractor_radius_y = 40
attractor_radius_dt_inc = 10

update_particles = 1
point_size = 4.0
speed_factor = 2.0
force_damping = 200.0
force_scale = 0.2
velocity_damping = 0.986
left_ctrl_key = 0
left_shift_key = 0
attractor_force_sign = 1
negative_force_duration = 0.0 -- in seconds
show_attractor = 1

keyboard_last_time = last_time

geexlab_ver = {x=0, y=0, z=0, w=0}
geexlab_ver.x, geexlab_ver.y, geexlab_ver.z, geexlab_ver.w = gh_utils.get_app_version()
geexlab_version = string.format("%s: %d.%d.%d.%d", host_app_name, geexlab_ver.x, geexlab_ver.y, geexlab_ver.z, geexlab_ver.w)

blending_factors =  {s=1, d=1}

bgcolor = {r=0.2, g=0.2, b=0.2, a=1.0}
particle_color = {r=1.0, g=1.0, b=1.0, a=1.0}

