local fov_max = 80.0
local fov_min = 10.0 -- max zoom level (smaller fov is more zoom)
local zoomspeed = 2.0 -- camera zoom speed
local speed_lr = 3.0 -- speed by which the camera pans left-right
local speed_ud = 3.0 -- speed by which the camera pans up-down
local toggle_helicam = 51 -- control id of the button by which to toggle the helicam mode. Default: INPUT_CONTEXT (E)
local toggle_vision = 25 -- control id to toggle vision mode. Default: INPUT_AIM (Right mouse btn)
local toggle_rappel = 154 -- control id to rappel out of the heli. Default: INPUT_DUCK (X)
local toggle_spotlight = 74 -- control id to toggle the front spotlight Default: INPUT_VEH_HEADLIGHT (H)
local toggle_lock_on = 22 -- control id to lock onto a vehicle with the camera. Default is INPUT_SPRINT (spacebar)
local spotlight_state = false

-- Script starts here
local helicam = false
local fov = (fov_max+fov_min)*0.5
local vision_state = 0 -- 0 is normal, 1 is nightmode, 2 is thermal vision

local isScanning = false
local isScanned = false
local scanValue = 0

local vehicle_detected = nil
local locked_on_vehicle = nil

-- Functions
local function IsPlayerInPolmav()
	local lPed = PlayerPedId()
	local vehicle = GetVehiclePedIsIn(lPed)
	return IsVehicleModel(vehicle, GetHashKey(Config.PoliceHelicopter))
end

local function IsHeliHighEnough(heli)
	return GetEntityHeightAboveGround(heli) > 1.5
end

local function ChangeVision()
	if vision_state == 0 then
		SetNightvision(true)
		vision_state = 1
	elseif vision_state == 1 then
		SetNightvision(false)
		SetSeethrough(true)
		vision_state = 2
	else
		SetSeethrough(false)
		vision_state = 0
	end
end

local function HideHUDThisFrame()
	HideHelpTextThisFrame()
	HideHudAndRadarThisFrame()
	HideHudComponentThisFrame(19) -- weapon wheel
	HideHudComponentThisFrame(1) -- Wanted Stars
	HideHudComponentThisFrame(2) -- Weapon icon
	HideHudComponentThisFrame(3) -- Cash
	HideHudComponentThisFrame(4) -- MP CASH
	HideHudComponentThisFrame(13) -- Cash Change
	HideHudComponentThisFrame(11) -- Floating Help Text
	HideHudComponentThisFrame(12) -- more floating help text
	HideHudComponentThisFrame(15) -- Subtitle Text
	HideHudComponentThisFrame(18) -- Game Stream
end

local function CheckInputRotation(cam, zoomvalue)
	local rightAxisX = GetDisabledControlNormal(0, 220)
	local rightAxisY = GetDisabledControlNormal(0, 221)
	local rotation = GetCamRot(cam, 2)
	if rightAxisX ~= 0.0 or rightAxisY ~= 0.0 then
		local new_z = rotation.z + rightAxisX*-1.0*(speed_ud)*(zoomvalue+0.1)
		local new_x = math.max(math.min(20.0, rotation.x + rightAxisY*-1.0*(speed_lr)*(zoomvalue+0.1)), -89.5) -- Clamping at top (cant see top of heli) and at bottom (doesn't glitch out in -90deg)
		SetCamRot(cam, new_x, 0.0, new_z, 2)
	end
end

local function HandleZoom(cam)
	if IsControlJustPressed(0,241) then -- Scrollup
		fov = math.max(fov - zoomspeed, fov_min)
	end
	if IsControlJustPressed(0,242) then
		fov = math.min(fov + zoomspeed, fov_max) -- ScrollDown
	end
	local current_fov = GetCamFov(cam)
	if math.abs(fov-current_fov) < 0.1 then -- the difference is too small, just set the value directly to avoid unneeded updates to FOV of order 10^-5
		fov = current_fov
	end
	SetCamFov(cam, current_fov + (fov - current_fov)*0.05) -- Smoothing of camera zoom
end

local function RotAnglesToVec(rot) -- input vector3
	local z = math.rad(rot.z)
	local x = math.rad(rot.x)
	local num = math.abs(math.cos(x))
	return vector3(-math.sin(z)*num, math.cos(z)*num, math.sin(x))
end

local function GetVehicleInView(cam)
	local coords = GetCamCoord(cam)
	local forward_vector = RotAnglesToVec(GetCamRot(cam, 2))
	--DrawLine(coords, coords+(forward_vector*100.0), 255,0,0,255) -- debug line to show LOS of cam
	local rayhandle = CastRayPointToPoint(coords, coords+(forward_vector*400.0), 10, GetVehiclePedIsIn(PlayerPedId()), 0)
	local _, _, _, _, entityHit = GetRaycastResult(rayhandle)
	if entityHit>0 and IsEntityAVehicle(entityHit) then
		return entityHit
	else
		return nil
	end
end

local function RenderVehicleInfo(vehicle)
	local pos = GetEntityCoords(vehicle)
	local model = GetEntityModel(vehicle)
	local vehname = GetLabelText(GetDisplayNameFromVehicleModel(model))
	local licenseplate = QBCore.Functions.GetPlate(vehicle)
	local speed = math.ceil(GetEntitySpeed(vehicle) * 3.6)
	local street1, street2 = GetStreetNameAtCoord(pos.x, pos.y, pos.z)
	local streetLabel = GetStreetNameFromHashKey(street1)
	if street2 ~= 0 then
		streetLabel = streetLabel .. " | " .. GetStreetNameFromHashKey(street2)
	end
	SendNUIMessage({
		type = "heliupdateinfo",
		model = vehname,
		plate = licenseplate,
		speed = speed,
		street = streetLabel,
	})
end

-- Events
RegisterNetEvent('heli:spotlight', function(serverID, state)
	local heli = GetVehiclePedIsIn(GetPlayerPed(GetPlayerFromServerId(serverID)), false)
	SetVehicleSearchlight(heli, state, false)
end)

-- Threads
CreateThread(function()
	while true do
		Wait(0)
		if LocalPlayer.state.isLoggedIn then
			if PlayerJob.name == 'police' and onDuty then
				if IsPlayerInPolmav() then
					local lPed = PlayerPedId()
					local heli = GetVehiclePedIsIn(lPed)

					if IsHeliHighEnough(heli) then
						if IsControlJustPressed(0, toggle_helicam) then -- Toggle Helicam
							PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
							helicam = true
							SendNUIMessage({
								type = "heliopen",
							})
						end

						if IsControlJustPressed(0, toggle_rappel) then -- Initiate rappel
							if GetPedInVehicleSeat(heli, 1) == lPed or GetPedInVehicleSeat(heli, 2) == lPed then
								PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
								TaskRappelFromHeli(PlayerPedId(), 1)
							end
						end
					end

					if IsControlJustPressed(0, toggle_spotlight) and (GetPedInVehicleSeat(heli, -1) == lPed or GetPedInVehicleSeat(heli, 0) == lPed) then
						spotlight_state = not spotlight_state
						TriggerServerEvent("heli:spotlight", spotlight_state)
						PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
					end

					if helicam then
						SetTimecycleModifier("heliGunCam")
						SetTimecycleModifierStrength(0.3)
						local scaleform = RequestScaleformMovie("HELI_CAM")
						while not HasScaleformMovieLoaded(scaleform) do
							Wait(0)
						end
						local cam = CreateCam("DEFAULT_SCRIPTED_FLY_CAMERA", true)
						AttachCamToEntity(cam, heli, 0.0,0.0,-1.5, true)
						SetCamRot(cam, 0.0,0.0,GetEntityHeading(heli))
						SetCamFov(cam, fov)
						RenderScriptCams(true, false, 0, 1, 0)
						PushScaleformMovieFunction(scaleform, "SET_CAM_LOGO")
						PushScaleformMovieFunctionParameterInt(0) -- 0 for nothing, 1 for LSPD logo
						PopScaleformMovieFunctionVoid()
						locked_on_vehicle = nil
						while helicam and not IsEntityDead(lPed) and (GetVehiclePedIsIn(lPed) == heli) and IsHeliHighEnough(heli) do
							if IsControlJustPressed(0, toggle_helicam) then -- Toggle Helicam
								PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
								helicam = false
								isScanned = false
								scanValue = 0
								SendNUIMessage({
									type = "disablescan",
								})
								SendNUIMessage({
									type = "heliclose",
								})
							end
							if IsControlJustPressed(0, toggle_vision) then
								PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
								ChangeVision()
							end
							local zoomvalue = 0
							if locked_on_vehicle then
								if DoesEntityExist(locked_on_vehicle) then
									PointCamAtEntity(cam, locked_on_vehicle, 0.0, 0.0, 0.0, true)
									if IsControlJustPressed(0, toggle_lock_on) then
										PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
										locked_on_vehicle = nil
										local rot = GetCamRot(cam, 2) -- All this because I can't seem to get the camera unlocked from the entity
										fov = GetCamFov(cam)
										local old_cam = cam
										DestroyCam(old_cam, false)
										cam = CreateCam("DEFAULT_SCRIPTED_FLY_CAMERA", true)
										AttachCamToEntity(cam, heli, 0.0,0.0,-1.5, true)
										SetCamRot(cam, rot, 2)
										SetCamFov(cam, fov)
										RenderScriptCams(true, false, 0, 1, 0)
										isScanned = false
										scanValue = 0
										SendNUIMessage({
											type = "disablescan",
										})
									end
								else
									isScanned = false
									SendNUIMessage({
										type = "disablescan",
									})
									locked_on_vehicle = nil -- Cam will auto unlock when entity doesn't exist anyway
								end
							else
								zoomvalue = (1.0/(fov_max-fov_min))*(fov-fov_min)
								CheckInputRotation(cam, zoomvalue)
								vehicle_detected = GetVehicleInView(cam)
								if DoesEntityExist(vehicle_detected) then
									isScanning = true
								else
									isScanning = false
								end
							end
							HandleZoom(cam)
							HideHUDThisFrame()
							PushScaleformMovieFunction(scaleform, "SET_ALT_FOV_HEADING")
							PushScaleformMovieFunctionParameterFloat(GetEntityCoords(heli).z)
							PushScaleformMovieFunctionParameterFloat(zoomvalue)
							PushScaleformMovieFunctionParameterFloat(GetCamRot(cam, 2).z)
							PopScaleformMovieFunctionVoid()
							DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255)
							Wait(0)
						end
						helicam = false
						ClearTimecycleModifier()
						fov = (fov_max+fov_min)*0.5 -- reset to starting zoom level
						RenderScriptCams(false, false, 0, 1, 0) -- Return to gameplay camera
						SetScaleformMovieAsNoLongerNeeded(scaleform) -- Cleanly release the scaleform
						DestroyCam(cam, false)
						SetNightvision(false)
						SetSeethrough(false)
					end
				else
					Wait(2000)
				end
			else
				Wait(2000)
			end
		else
			Wait(2000)
		end
	end
end)

CreateThread(function()
	while true do
		Wait(1)
		if helicam then
			if isScanning and not isScanned then
				if scanValue < 100 then
					scanValue = scanValue + 1
					SendNUIMessage({
						type = "heliscan",
						scanvalue = scanValue,
					})
					if scanValue == 100 then
						PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
						locked_on_vehicle = vehicle_detected
						isScanning = false
						isScanned = true
					end
					Wait(10)
				end
			elseif isScanned and not isScanning and locked_on_vehicle then
				scanValue = 100
				RenderVehicleInfo(locked_on_vehicle)
				isScanning = false
				Wait(100)
			else
				scanValue = 0
				Wait(500)
			end
		else
			Wait(1000)
		end
	end
end)