

local yaw   = 0.0
local last_yaw = 0.0
local init_yaw = 0.0

local pitch = 0.0
local last_pitch = 0.0
local init_pitch = 0.0

local roll  = 0.0
local last_roll  = 0.0
local init_roll = 0.0

local inputProjections = {
	"hequirect",
	"equirect",
	"fisheye",
	"pannini",
	"cylindrical",
	"sg"
}
local inputProjectionInd = 0
local inputProjection    = "hequirect"

local outputProjections = {
	"flat",
	"sg"
}

local outputProjectionInd = 0
local outputProjection    = "flat"

local idfov=180.0
local dfov=110.0
local last_dfov  = 110.0
local init_dfov = 0.0


local doit = 0.0
local res  = 1.0
local dragging = false

local smoothMouse = true

local scaling   = 'linear'

local in_stereo = 'sbs'

local h_flip    = '0'
local in_flip   = ''

local interp    = 'cubic'

local startTime = nil

local filterIsOn = false

local mousePos = {}
local lasttimePos = nil
local filename = nil

local fileobjectNumber = 0
local fileobjectFilename = ''
local videofilename = ''
local file_object      = nil

local ffmpegComamndList = {}

local openNewLogFile = function()
	if lasttimePos ~= nil then
		fileobjectNumber = fileobjectNumber+1
	end
	videofilename = mp.get_property('filename')
	fileobjectFilename = string.format('%s_3dViewHistory_%s.txt',videofilename,fileobjectNumber)
	file_object = io.open(fileobjectFilename, 'w')
	lasttimePos=nil
end


function SecondsToClock(seconds)
  local seconds = tonumber(seconds)
  if seconds <= 0 then
    return "00:00:00";
  else
    hours = string.format("%02.f", math.floor(seconds/3600));
    mins = string.format("%02.f", math.floor(seconds/60 - (hours*60)));
    secs = string.format("%02.2f", seconds - hours*3600 - mins *60);
    return hours..":"..mins..":"..secs
  end
end

local writeHeadPositionChange = function()

	if filename == nil then
		filename = mp.get_property("path")
	end

	if file_object == nil then
		return
	else		
		local initPass=false
		if lasttimePos == nil then
			lasttimePos = mp.get_property("time-pos")
			startTime   = lasttimePos
			initPass=true
			if lasttimePos == nil then
				return
			end
		end

		local newTimePos = mp.get_property("time-pos")

		if newTimePos == nil then
			return
		end

		local outputTs = string.format("%.3f-%.3f ",lasttimePos,newTimePos)
		local changedValues = {}
		local movementDuration = (newTimePos-lasttimePos)
		local maximumTimeoutReached = movementDuration > 5.0

		if initPass or pitch ~= last_pitch or maximumTimeoutReached then
			changedValues[#changedValues+1]= string.format(", [expr] v360 pitch 'lerp(%.3f,%.3f,(T-%.3f)/%.3f)'",last_pitch,pitch,lasttimePos,movementDuration)
		end 
		last_pitch=pitch

		if initPass or yaw ~= last_yaw or maximumTimeoutReached then
			changedValues[#changedValues+1]= string.format(", [expr] v360 yaw 'lerp(%.3f,%.3f,(T-%.3f)/%.3f)'",last_yaw,yaw,lasttimePos,movementDuration)
		end 
		last_yaw=yaw


		if initPass or roll ~= last_roll or maximumTimeoutReached then
			changedValues[#changedValues+1]= string.format(", [expr] v360 roll 'lerp(%.3f,%.3f,(T-%.3f)/%.3f)'",last_roll,roll,lasttimePos,movementDuration)
		end 
		last_roll=roll

		if initPass or dfov ~= last_dfov or maximumTimeoutReached then
			changedValues[#changedValues+1]= string.format(", [expr] v360 d_fov 'lerp(%.3f,%.3f,(T-%.3f)/%.3f)'",last_dfov,dfov,lasttimePos,movementDuration)
		end 
		last_dfov=dfov

		if initPass then
			init_pitch = pitch
			init_yaw   = yaw
			init_roll  = roll
			init_dfov  = dfov
		end

		if #changedValues > 0 then
			local commandString = ''
			for k,changedValue in pairs(changedValues) do
				commandString = commandString .. changedValue
			end

			commandString = commandString:sub(2)

			commandString = outputTs .. commandString .. ';'

			file_object:write(commandString .. '\n')	
			lasttimePos = newTimePos
		end
	end
end

local updateAwaiting = false
local updateComplete = function()
	updateAwaiting = false
end

local printRecordingStatus = function()
	local startts = startTime
	local endts   = lasttimePos
	local currenttS = mp.get_property("time-pos")
	if file_object ~= nil and endts ~= nil and startts ~= nil then
		endts = math.max(endts,currenttS)
		mp.osd_message(string.format("Recording:%s",SecondsToClock(endts-startts)),10)
	end
end


local updateFilters = function ()

	if not filterIsOn then
		mp.command_native_async({"no-osd", "vf", "add", string.format("@vrrev:%sv360=%s:%s:in_stereo=%s:out_stereo=2d:id_fov=%s:d_fov=%.3f:yaw=%.3f:pitch=%s:roll=%.3f:w=%s*192.0:h=%.3f*108.0:h_flip=%s:interp=%s",in_flip,inputProjection,outputProjection,in_stereo,idfov,dfov,yaw,pitch,roll,res,res,h_flip,scaling)}, updateComplete)
		filterIsOn=true
	else
		if not updateAwaiting then
			updateAwaiting=true
			mp.command_native_async({"no-osd", "vf", "set", string.format("@vrrev:%sv360=%s:%s:in_stereo=%s:out_stereo=2d:id_fov=%s:d_fov=%.3f:yaw=%.3f:pitch=%s:roll=%.3f:w=%s*192.0:h=%.3f*108.0:h_flip=%s:interp=%s",in_flip,inputProjection,outputProjection,in_stereo,idfov,dfov,yaw,pitch,roll,res,res,h_flip,scaling)}, updateComplete)
		end
		filterIsOn=true
	end
	printRecordingStatus()
	writeHeadPositionChange()
end

local mouse_btn0_cb = function ()
	dragging = not dragging
	if dragging then
		mp.set_property("cursor-autohide", "always")
	else
		mp.set_property("cursor-autohide", "no")
	end 
end


local mouse_pan = function ()
	
	
	if dragging then

		local MousePosx, MousePosy = mp.get_mouse_pos()
		local osd_w, osd_h = mp.get_property("osd-width"), mp.get_property("osd-height")


		local yawpc 	= ((MousePosx/osd_w)-0.5)*180
		local pitchpc   = -((MousePosy/osd_h)-0.5)*180

		local updateCrop = false

		if smoothMouse then
			if yaw ~= yawpc and math.abs(yaw-yawpc)<0.1 then
				yaw = yawpc
				updateCrop=true
				yaw = math.max(-180,math.min(180,yaw))
			elseif yaw ~= yawpc then
				yaw   = (yawpc+(yaw*5))/6
				yaw = math.max(-180,math.min(180,yaw))
				updateCrop=true
			end

			if pitch ~= pitchpc and math.abs(pitch-pitchpc)<0.1 then
				pitch = pitchpc
				pitch = math.max(-180,math.min(180,pitch))
				updateCrop=true
			elseif pitch ~= pitchpc then
				pitch = (pitchpc+(pitch*5))/6
				pitch = math.max(-180,math.min(180,pitch))
				updateCrop=true
			end
		else
			if yaw ~= yawpc then 
				yaw  = yawpc
				yaw = math.max(-180,math.min(180,yaw))
				updateCrop=true
			end
			if pitch ~= pitchpc then 
				pitch  = pitchpc
				pitch = math.max(-180,math.min(180,pitch))
				updateCrop=true
			end

		end

		if updateCrop then
			updateFilters()
		end

	end
end

local increment_res = function(inc)
	res = res+inc
	res = math.max(math.min(res,20),1)
	mp.osd_message(string.format("Out-Width: %spx",res*108.0),0.5)
	updateFilters()
end

local increment_roll = function (inc)
	roll = roll+inc
	updateFilters()
	mp.osd_message(string.format("Roll: %s°",roll),0.5)
end

local increment_pitch = function (inc)
	pitch = pitch+inc
	updateFilters()
end

local increment_yaw = function (inc)
	yaw = yaw+inc
	updateFilters()
end

local increment_zoom = function (inc)
	dfov = dfov+inc
	dfov = math.max(math.min(150,dfov),30)
	mp.osd_message(string.format("D-Fov: %s°",dfov),0.5)
	updateFilters()
end

local toggleSmoothMouse  = function()
	smoothMouse = not smoothMouse
	if smoothMouse then
		mp.osd_message("Mouse smothing On",0.5)
	else
		mp.osd_message("Mouse smothing Off",0.5)
	end
end

local switchScaler = function()
	if scaling == 'nearest' then
		scaling = 'cubic'
	elseif scaling == 'cubic' then
		scaling = 'lagrange9'
	elseif scaling == 'lagrange9' then
		scaling = 'lanczos'
	elseif scaling == 'lanczos' then
		scaling = 'linear'
	elseif scaling == 'linear' then
		scaling = 'nearest'
	end
	mp.osd_message("Scaling algorithm: " .. scaling,5.5)
	updateFilters()
end

local switchEye = function()
	if h_flip == '0' then
		h_flip  = '1'
		in_flip = 'hflip,'
		mp.osd_message("Right eye",0.5)
	else
		h_flip  = '0'
		in_flip = ''
		mp.osd_message("Left eye",0.5)
	end
	print(ih_flip,h_flip)
	updateFilters()
end


local cycleInputProjection = function()
	inputProjectionInd = ((inputProjectionInd+1) % (#inputProjections +1))
	inputProjection    = inputProjections[inputProjectionInd]
	mp.osd_message(string.format("Input projection: %s ",inputProjection),0.5)
	updateFilters()
end

local cycleOutputProjection = function()
	outputProjectionInd = ((outputProjectionInd+1) % (#outputProjections + 1))
	outputProjection    = outputProjections[outputProjectionInd]
	mp.osd_message(string.format("Output projection: %s",outputProjection),0.5)
	updateFilters()
end


local switchInputFovBounds = function()
	if idfov == 180.0 then
			idfov = 360.0
	elseif idfov == 360.0 then
		idfov = 90.0
	else
		idfov = 180.0
	end
	mp.osd_message(string.format("Input fov bounds: %s°",idfov),0.5)
	updateFilters()
end

local switchStereoMode = function()
	if in_stereo == 'sbs' then
		in_stereo = 'tb'
	else
		in_stereo = 'sbs'
	end
	mp.osd_message("Input format: " .. in_stereo,0.5)
	updateFilters()
end

local showHelp  = function()
	mp.osd_message("Keyboard and Mouse Controls:\n? = show help\ny,h = adjust quality\ni,j,k,l,mouseClick = Look around\nu,i = roll head\n-,=,mouseWheel = zoom\nr = switch SetereoMode\nt = switch Eye\ne = switch Scaler\ng = toggle mouse smothing\nn = start and stop motion recording\n1,2 - cycle in and out projections",10)
end

local closeCurrentLog = function()
	commandForFinalLog=''
	if lasttimePos ~= nil  and file_object ~= nil then

		finalTimeStamp = mp.get_property("time-pos")

		file_object:write('#\n')

 		local stats = string.format( '# Duration: %s-%s (total %s) %s seconds', 
			SecondsToClock(startTime),SecondsToClock(finalTimeStamp),SecondsToClock(finalTimeStamp-startTime),finalTimeStamp-startTime )

		print('#')
		file_object:write( stats  .. '\n')
		print(stats)

		file_object:write( '# Suggested ffmpeg conversion command:\n')

		local closingCommandComment = string.format('ffmpeg -y -ss %s -i "%s" -to %s -copyts -filter_complex "%sv360=%s:%s:in_stereo=%s:out_stereo=2d:id_fov=%s:d_fov=%.3f:yaw=%.3f:pitch=%.3f:roll=%.3f:w=1920.0:h=1080.0:interp=cubic:h_flip=%s,sendcmd=filename=%s_3dViewHistory_%s.txt" -avoid_negative_ts make_zero -preset slower -crf 17 "%s_2d_%03d.mp4"',
			startTime,filename,finalTimeStamp,in_flip,inputProjection,outputProjection,in_stereo,idfov,init_dfov,init_yaw,init_pitch,init_roll,h_flip,videofilename,fileobjectNumber,videofilename,fileobjectNumber
		)

				
		file_object:write('# ' .. closingCommandComment .. '\n')
		file_object:write('#\n')

		print(closingCommandComment)
		print('#')

		commandForFinalLog = closingCommandComment
	end
	if file_object ~= nil then
		file_object:close()
		file_object = nil
	end
	return commandForFinalLog
end

local startNewLogSession = function()
	if file_object == nil then
		openNewLogFile()
		writeHeadPositionChange()
		mp.osd_message(string.format("Start Motion Record %s_3dViewHistory_%s.txt",videofilename,fileobjectNumber),0.5)
	else
		mp.osd_message(string.format("Stop Motion Record %s_3dViewHistory_%s.txt",videofilename,fileobjectNumber),0.5)
		writeHeadPositionChange()
		local command = closeCurrentLog()
		if command then
			ffmpegComamndList[#ffmpegComamndList+1] = command
		end
	end
		
end

local onExit = function()
	closeCurrentLog()
	mergedCommand = ''
	for k,v in pairs(ffmpegComamndList) do
		if v ~= '' then
			mergedCommand = mergedCommand .. ' & ' .. v
		end
	end
	if mergedCommand ~= '' then
		mergedCommand = mergedCommand:sub(3)
		print(mergedCommand)
		batchfile = io.open('convert_3dViewHistory.bat', 'w')
		batchfile:write(mergedCommand)
		batchfile:close()
		print('Batch processing file created convert_3dViewHistory.bat')
	else
		print('No head motions logged')
	end
end

local recordingStatusTimer = nil

local initFunction = function()

	mp.add_forced_key_binding("1", cycleInputProjection  )
	mp.add_forced_key_binding("2", cycleOutputProjection )

	mp.add_forced_key_binding("u", function() increment_roll(-1) end, 'repeatable')
	mp.add_forced_key_binding("o", function() increment_roll(1)  end, 'repeatable')

	mp.add_forced_key_binding("v", writeHeadPositionChange)

	mp.add_forced_key_binding("i", function() increment_pitch(1)  end, 'repeatable')
	mp.add_forced_key_binding("k", function() increment_pitch(-1) end, 'repeatable')
	mp.add_key_binding("l", function() increment_yaw(1)  end, 'repeatable')
	mp.add_key_binding("j", function() increment_yaw(-1) end, 'repeatable')
	mp.add_key_binding("c", "easy_crop", updateFilters)

	mp.add_forced_key_binding("y", function() increment_res(1)  end, 'repeatable')
	mp.add_forced_key_binding("h", function() increment_res(-1) end, 'repeatable')

	mp.add_forced_key_binding("=", function() increment_zoom(-1)  end, 'repeatable')
	mp.add_forced_key_binding("-", function() increment_zoom(1) end, 'repeatable')

	mp.add_forced_key_binding("WHEEL_DOWN", function() increment_zoom(1)  end)
	mp.add_forced_key_binding("WHEEL_UP",   function() increment_zoom(-1) end)

	mp.add_forced_key_binding("r", switchStereoMode)
	mp.add_forced_key_binding("t", switchEye)
	mp.add_forced_key_binding("e", switchScaler)
	mp.add_forced_key_binding("g", toggleSmoothMouse)
	mp.add_forced_key_binding("b", switchInputFovBounds)
	mp.add_forced_key_binding("n", startNewLogSession)

	mp.set_property("osc", "no")
	mp.set_property("fullscreen", "yes")
	mp.set_property("osd-font-size", "30")
	mp.add_forced_key_binding("mouse_btn0",mouse_btn0_cb)
	mp.add_forced_key_binding("mouse_move", mouse_pan)

	mp.add_forced_key_binding("?", showHelp)
	mp.add_forced_key_binding("/", showHelp)

	mp.register_event("end-file", onExit)
	mp.register_event("shutdown", onExit)

	recordingStatusTimer = mp.add_periodic_timer(0.1,printRecordingStatus)

	updateFilters()

end

mp.register_event("file-loaded", initFunction)