require 'luagl'
require 'luaglut'
require 'memarray'
require 'config'

local splif_version = '0.1b'
local splif_titletext = 'Trilobit ScriPtable Lua Interactive MultiplatForm (SPLIF) version ' .. splif_version
local debugmode = 0
local res_w = 0
local res_h = 0
local res_override = 0

local quit = false
local fps = 60
local fps_str = ''
local msec = 1000 / fps
local frames = 0
local cur_fps = 0
local start_time
local fps_time
local elapsed_time
local millis = 0
local timedelta = 0
local last_frame = 0
local row
local order
local ppm, width, height
local sync_set = 0
local debug_pause = 1
local seeked = 0
local song_time_end = 0
local init_done = 0
local effects = {}
local timeline = {}
local spectrum = {}

local mouse_x = 0
local mouse_y = 0

local mouse_over_slider = 0

local slider_x = 10
local slider_y = 10
local slider_w = 20
local slider_h = 10
local slider_bar_pos = 0

local function glutBitmapString(font, str)
        for i = 1, string.len(str) do
                glutBitmapCharacter(font, string.byte(str, i))
        end
end

function deepcopy(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

function engine_load_effects()
	print("engine_load_effects")
	local fxlist = config_effects()
	for _, effect in pairs(fxlist) do
		res, errmsg = loadfile(effect.file)
		if errmsg == nil then
			res()
			effect.load = loadstring("return "..effect.name.."_load()")
			effect.unload = loadstring("return "..effect.name.."_unload()")
			effect.init = loadstring("return "..effect.name.."_init")()
			effect.deinit = loadstring("return "..effect.name.."_deinit()")
			effect.paint = loadstring("return "..effect.name.."_paint")()
			effect.load()
		else
			print("Error in engine_load_effects(): " .. errmsg)
			return false
		end
	end
	loading = 0
	effects = fxlist
	return engine_make_timeline()
end

function engine_get_effect(name) 
	for _, effect in pairs(effects) do
		if effect.name == name then
			return effect
		end
	end
	return false
end

function engine_make_timeline()
	temptimeline = {}
	temptimeline.current = nil
	temptimeline.current_start = nil
	temptimeline.current_end = nil
	temptimeline.effects = {}
	
	for _, timing in pairs(config_timing()) do
		table.insert(temptimeline.effects, #temptimeline.effects + 1, timing)
	end

	local prev_timing = 0

	for _, timing in pairs(temptimeline.effects) do
		if not timing.start then
			if not timing.delta then
				print ("timing.delta info missing and no start specified for effect.")
				os.exit()
			else
				timing.start = prev_timing.start + timing.delta
			end
		end
		prev_timing = timing
	end

	table.sort(temptimeline.effects, function(a,b)  return tonumber(a.start) <= tonumber(b.start) end)
	
	for _, timing in pairs(temptimeline.effects) do
		--print(timing.start .. ": " ..timing.effect)
	end
	
	timeline = temptimeline
	
	engine_next_effect()
	
	return true
end

function engine_spectrum() 
	
	if debug_pause == 1 then
	
		temp = song_spectrum()
		for i = 0, 511 do
			spectrum[i] = ( spectrum[i] * 0.3 ) + (temp[i] *0.7)
		end
	
	end

	return spectrum;
end

function engine_next_effect()
	local selected = nil
	local selected_start = 0
	local selected_end = nil

	for _, timing in pairs(timeline.effects) do
		if millis >= timing.start then 
			selected = timing.effect
			selected_start = timing.start
			selected_params = timing.params
		else
			if selected_end == nil then
				selected_end = timing.start
				break
			end
		end
		
	end
	
	selected = engine_get_effect(selected)
	
	--print(millis .. " switch to " .. selected.name)
	
	if selected_end == nil then
		selected_end = 999999
	end
	
	last = timeline.current
	
	timeline.current_start = millis
	timeline.current_end = selected_end
	timeline.current_params = selected_params
	timeline.current = selected
	
	timeline.current.init(millis)
end

function engine_main() 

	res_override, res_w, res_h, res_hz, res_windowed = demo_getresolution()
	
	for i = 0, 511 do
		spectrum[i] = 0.5
	end

	glutInit(arg)

	if res_override == 1 then
		if res_windowed >= 1 then glutInitWindowSize(res_w, res_h)
		else glutGameModeString(res_w .. "x" .. res_h .. ":32" .. "@" .. res_hz)
		end
	end

	local jit_version = "(N/A)"

	if jit then
		if jit.version then
			jit_version = jit.version
		end
	end

	print('[ ' .. splif_titletext .. ' ]')
	print('[ Running on Lua: "' .. _VERSION .. '" LuaJIT: "' .. jit_version .. '" LuaGL: "' .. luagl.VERSION .. '" LuaGLUT: "' .. luaglut.VERSION .. '" FMOD Ex: "' .. song_getfmodversion() .. '" ]')

	glutInitDisplayMode(GLUT_RGB + GLUT_DOUBLE + GLUT_DEPTH + GLUT_MULTISAMPLE)
	if arg then title = arg[0] else title = splif_titletext end
	if res_windowed >= 1 then window = glutCreateWindow(title)
	else window = glutEnterGameMode()
	end

	if res_windowed == 2 then glutFullScreen() end
	
	glutSetCursor(GLUT_CURSOR_NONE)

	glutDisplayFunc(display_func)
	glutKeyboardFunc(keyboard_func)
	glutReshapeFunc(resize_func)
	glutTimerFunc(msec, timer_func, 0)
	
	-- rodent stuff
	glutMouseFunc(mouse_func)
	glutMotionFunc(mouse_motion)
	glutPassiveMotionFunc(mouse_motion)
	
	
	
	song_time_end = song_getlength()

	-- hack for osx tearing and other misc inits

	demosystem_init()

	if res_override == 1 and res_windowed == 0 then
		print('[ Requested screen mode: ' .. res_w .. 'x' .. res_h .. ' ('  .. res_hz .. 'Hz) ]')
	end
	if res_windowed == 0 then print ("[ Screen mode: " .. glutGameModeGet(GLUT_GAME_MODE_WIDTH) .. "x" .. glutGameModeGet(GLUT_GAME_MODE_HEIGHT) .. "x" .. glutGameModeGet(GLUT_GAME_MODE_PIXEL_DEPTH) .. " (" .. glutGameModeGet(GLUT_GAME_MODE_REFRESH_RATE) .. "Hz) ]") end


	engine_load_effects()
	start_time = glutGet(GLUT_ELAPSED_TIME)
	fps_time = start_time
	print("Are you experienced?")

	glutMainLoop()
	
end


function debug()
	glDisable(GL_BLEND)
	glDisable(GL_TEXTURE_2D)
	glMatrixMode(GL_PROJECTION)
	glPushMatrix()
	glLoadIdentity()
	glOrtho(0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT), -3, 3)
	if debugmode == 1 then
		if sync_set > 0 then
			sync_str = '[sync #' .. sync_set .. ' set]'
		else
			sync_str = ''
		end
		fps_str = 'effect: '.. timeline.current.name ..' millis: [' .. millis .. '] order: [' .. order .. '] row: [' .. row .. '] fps: [' .. cur_fps .. '] ' .. sync_str
		glColor3d(1,1,1)
		glRasterPos2d(2,glutGet(GLUT_WINDOW_HEIGHT)-16)
		glutBitmapString(GLUT_BITMAP_8_BY_13, fps_str) 
		glColor3d(0.2,0.2,0.2)
		glRasterPos2d(2,glutGet(GLUT_WINDOW_HEIGHT)-30)
		glutBitmapString(GLUT_BITMAP_8_BY_13, fps_str )
		
		-- susiruma cursori, tein itse, säästin
		glColor3d(1,1,0.2)
		y1=glutGet(GLUT_WINDOW_HEIGHT)-mouse_y;
		glBegin(GL_TRIANGLES)
		glVertex3d(mouse_x, y1,0)
		glVertex3d(mouse_x+10, y1,0)
		glVertex3d(mouse_x, y1-10,0)
		glEnd()
		
		draw_song_slider()
	end
	glPopMatrix()
end

function draw_song_slider() 
	local song_duration = song_getlength()
	
	
	
	
	
	slider_bar_pos = slider_w * (millis/song_duration)
	
	
	slider_x = 10
	slider_y = 10
	slider_w = glutGet(GLUT_WINDOW_WIDTH)-20
	slider_h = 10
	
	if mouse_over_slider == 1 then
		glColor3d(0.9,0.9,0.9)
	else
		glColor3d(0.5,0.5,0.5)
	end
	
	glBegin(GL_QUADS)
	
	glVertex3d(slider_x, slider_y,-0.1)
	glVertex3d(slider_x+slider_w, slider_y,-0.1)
	glVertex3d(slider_x+slider_w, slider_y+slider_h,-0.1)
	glVertex3d(slider_x, slider_y+slider_h,-0.1)
	
	glEnd();
	
	glColor3d(0,0,0)
	
	glBegin(GL_QUADS)
	
	glVertex3d(slider_x+slider_bar_pos, slider_y,0)
	glVertex3d(slider_x+slider_bar_pos+5, slider_y,0)
	glVertex3d(slider_x+slider_bar_pos+5, slider_y+slider_h,0)
	glVertex3d(slider_x+slider_bar_pos, slider_y+slider_h,0)
	
	glEnd();
	
end

function display_func()
	if quit then return end

	elapsed_time = glutGet(GLUT_ELAPSED_TIME) - fps_time

	last_frame = millis
	millis = song_getmillis()
	if millis == nil then millis = 0 end
	row = song_getrow()
	order = song_getorder()

	timedelta = millis-last_frame
	if seeked == 1 then
		--print("seek check")
		engine_next_effect()
		seeked = false
	end

	if millis >= timeline.current_end then
		engine_next_effect()
	end
	
	timeline.current.paint(millis, timedelta, timeline.current_params)

	
	debug()

	if fps_time > 5000 and init_done == 0 then
		if init_done == 0 then song_play() end
		init_done = 1
	elseif fps_time < 5000 then
		glClearColor(0,0,0,1)
		glClear(GL_COLOR_BUFFER_BIT)
	end

	glutSwapBuffers()

	if millis >= 124909 then
                quit = true
		if res_windowed == 0 then glutLeaveGameMode() end
                glutDestroyWindow(window)
                os.exit(0)
	end

	frames = frames + 1
	if elapsed_time > 1000 then
		cur_fps = frames * 1000 / elapsed_time
		fps_time = fps_time + elapsed_time
		frames = 0
	end

end


function resize_func(w, h)
	local ratio = w / h
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()
	if w < h then
		glViewport(0,0,w,w)
		ratio = w/w
	else
		local center_x = 0
		if w > 2560 then 
			center_x = (w / 2) - (2560/2)
			w = 2560
		end
		if h > 1440 then h = 1440
		end
		
		ratio = w / h
		glViewport(center_x,0,w,h)
	end
	gluPerspective(45,ratio,1,10000)
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()
	glEnable(GL_DEPTH_TEST)
	glEnable(GL_NORMALIZE)
end

function timer_func()
	if quit then return end

	glutSetWindow(window)
	glutTimerFunc(msec, timer_func, 0)
	glutPostRedisplay()
end

function keyboard_func(key,x,y)
	if key == 27 then
		quit = true
		if res_windowed == 0 then glutLeaveGameMode() end
		glutDestroyWindow(window)
		os.exit(0)
	end
	if key == 32 then
		debugmode = -debugmode
	end

	
	if debugmode == 1 then
	
		if key == 114 then
			--print("reload")
			song_play()
			millis = song_getmillis()
			row = song_getrow()
			order = song_getorder()
			engine_load_effects()
		end 
		-- syncpoints
		if key >= 49 and key <= 57 then
			print('sync #' .. key-48 .. ', millis: ' .. millis .. ' order: ' .. order .. ' row: ' .. row)
			sync_set = key-48
		end

		-- song seeking
		if key == 97 then
			seeked = 1
			song_seek(0)
		end
		if key == 100 then
			seeked = 1
			song_seek(1)
		end
		if key == 119 then
			seeked = 1
			song_seek(2)
		end
		if key == 115 then
			seeked = 1
			song_seek(3)
		end
		
		if key == 122 then
			song_seek(4)
		end
		if key == 120 then
			song_seek(5)
		end
		if key == 112 then
			if debug_pause == 1 then 
				song_pause(0)
			else 
				song_pause(1)
			end
			debug_pause = -debug_pause
		end
	end


end

function mouse_func(button, state, x , y) 
	--print ("mouse_func button = " .. button .. " state = " .. state .. " x = " .. x .. " y = " .. y)
	mouse_x = x
	mouse_y = y
end

function mouse_entry(state) 
	--print ("mouse_entry state=" .. state)
end

function mouse_motion(x, y) 
	--print ("mouse move x = " .. x .. " y = " .. y)
	mouse_x = x
	mouse_y = y
	
	if debugmode == 1 then
		mouse_over_slider = 0
		
		-- glunproject mouse coordz
	
	end
end

engine_main()




