--[[------------------------------------------------------------------------

	Wraith ARS 2X - v1.0.0
	Created by WolfKnight

------------------------------------------------------------------------]]--

local next = next 
local dot = dot 
local table = table 
local type = type
local tostring = tostring
local math = math 
local pairs = pairs 

--[[------------------------------------------------------------------------
	Resource Rename Fix - for those muppets who rename the resource and 
	complain that the NUI aspect doesn't work!
------------------------------------------------------------------------]]--
Citizen.SetTimeout( 1000, function()
	-- Get the name of the resource, for example the default name is 'wk_wrs2'
	local name = GetCurrentResourceName()

	print( "WK_WARS2X: Sending resource name (" .. name .. ") to JavaScript side." )

	-- Send a message through the NUI system to the JavaScript file to give the name of the resource 
	SendNUIMessage( { _type = "updatePathName", pathName = name } )
end )


--[[------------------------------------------------------------------------
	Player info variables
------------------------------------------------------------------------]]--
PLY = {}
PLY.ped = PlayerPedId()
PLY.veh = nil 
PLY.inDriverSeat = false 
PLY.vehClassValid = false


--[[------------------------------------------------------------------------
	Radar variables
------------------------------------------------------------------------]]--
RADAR.vars = 
{
	-- The radar's power
	power = false, 
	poweringUp = false, 

	-- These are the settings that are used in the operator menu 
	settings = {
		["fastDisplay"] = true, 

		-- Sensitivty for each mode, 1-5
		["same"] = 4, 
		["opp"] = 3, 

		["alert"] = true,

		["beep"] = 1.0
	},

	menuActive = false, 
	currentOptionIndex = 1, 
	menuOptions = {
		{ displayText = { "¦¦¦", "FAS" }, optionsText = { "On¦", "Off" }, options = { true, false }, optionIndex = 1, settingText = "fastDisplay" },
		{ displayText = { "¦SL", "SEn" }, optionsText = { "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = 4, settingText = "same" },
		{ displayText = { "¦OP", "SEn" }, optionsText = { "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = 3, settingText = "opp" },
		{ displayText = { "¦¦b", "EEP" }, optionsText = { "¦1¦", "¦2¦", "¦3¦", "¦4¦", "¦5¦" }, options = { 0.2, 0.4, 0.6, 0.8, 1.0 }, optionIndex = 5, settingText = "beep" }
	},

	-- Player's vehicle speed, this is used to update the patrol vehicle speed on the radar
	patrolSpeed = 0,
	patrolLocked = false, 
	psBlank = false, 

	-- The speed type, this is used when converting speeds to a readable format
	-- Either "mph" or "kmh", can be toggle in-game 
	speedType = "mph",

	-- Antennas, this table contains all of the data needed for operation of the front and rear antennas 
	antennas = {
		-- Variables for the front antenna 
		[ "front" ] = {
			xmit = false,		-- Whether the antenna is on or off
			mode = 0,			-- Current antenna mode, 0 = none, 1 = same, 2 = opp, 3 = same and opp 
			--[[ speed = 0,			-- Speed of the vehicle caught by the front antenna 
			dir = nil, 			-- Direction the caught vehicle is going, 0 = towards, 1 = away
			fastMode = 1, 		-- Current fast mode, 1 = polling, 2 = lock on at first fast vehicle 
			fastSpeed = 0, 		-- Speed of the fastest vehicle caught by the front antenna
			fastDir = nil, 		-- Direction the fastest vehicle is going, 0 = towards, 1 = away  
			fastLocked = false	-- Whether the fast speed is locked or not ]]
		}, 

		[ "rear" ] = {
			xmit = false,		-- Whether the antenna is on or off
			mode = 0,			-- Current antenna mode, 0 = none, 1 = same, 2 = opp, 3 = same and opp 
			--[[ speed = 0,			-- Speed of the vehicle caught by the front antenna 
			dir = nil, 			-- Direction the caught vehicle is going, 0 = towards, 1 = away
			fastMode = 1, 		-- Current fast mode, 1 = polling, 2 = lock on at first fast vehicle 
			fastSpeed = 0, 		-- Speed of the fastest vehicle caught by the front antenna
			fastDir = nil, 		-- Direction the fastest vehicle is going, 0 = towards, 1 = away  
			fastLocked = false	-- Whether the fast speed is locked or not ]]
		}
	}, 

	-- The maximum distance that the radar system's ray traces can go 
	maxCheckDist = 300.0,

	-- Cached dynamic vehicle sphere sizes, automatically populated when the system is running 
	sphereSizes = {}, 

	-- Table to store tables for hit entities of captured vehicles 
	capturedVehicles = {},

	-- Table for temp id storage to stop unnecessary trace checks
	tempVehicleIDs = {},

	-- The current vehicle data for display 
	activeVehicles = {},

	-- Vehicle pool, automatically populated when the system is running, holds all of the current
	-- vehicle IDs for the player using entity enumeration (see cl_utils.lua) 
	vehiclePool = {}, 

	-- Radar stage, this is used to tell the system what it should currently be doing, the stages are:
	--    - 0 = Gathering vehicles hit by the rays 
	--    - 1 = Filtering the vehicles caught (removing duplicates, etc) and calculating what needs to be shown 
	--	        to the user based on modes and settings
	--    - 2 = Sending all required data across to the NUI system for display 
	radarStage = 0,

	-- Ray trace state, this is used so the radar stage doesn't progress to the next stage unless 
	-- all of the ray trace threads have completed 
	rayTraceState = 0,

	-- Number of ray traces, automatically cached when the system first runs 
	numberOfRays = 0
}

-- These vectors are used in the custom ray tracing system 
RADAR.rayTraces = {
	-- { startVec = { x = 0.0 }, endVec = { x = 0.0, y = 200.0 }, rayType = "same" },
	-- { startVec = { x = -5.0 }, endVec = { x = -5.0, y = 200.0 }, rayType = "same" },
	-- { startVec = { x = 5.0 }, endVec = { x = 5.0, y = 200.0 }, rayType = "same" },
	{ startVec = { x = 3.0 }, endVec = { x = 3.0, y = 200.0, baseY = 200.0 }, rayType = "same" },
	{ startVec = { x = -3.0 }, endVec = { x = -3.0, y = 200.0, baseY = 200.0 }, rayType = "same" },
	{ startVec = { x = -10.0 }, endVec = { x = -10.0, y = 200.0, baseY = 200.0 }, rayType = "opp" },
	{ startVec = { x = -16.0 }, endVec = { x = -16.0, y = 200.0, baseY = 200.0 }, rayType = "opp" }
}

-- Each of these are used for sorting the captured vehicle data, the 'strongest' filter is used for the main 
-- target window of each antenna, whereas the 'fastest' filter is used for the fast target window of each antenna
RADAR.sorting = {
	strongest = function( a, b ) return a.size > b.size end, 
	fastest = function( a, b ) return a.speed > b.speed end
}


--[[------------------------------------------------------------------------
	Radar essentials functions  
------------------------------------------------------------------------]]--
function RADAR:IsPowerOn()
	return self.vars.power 
end 

function RADAR:IsPoweringUp()
	return self.vars.poweringUp
end 

function RADAR:SetPoweringUpState( state )
	self.vars.poweringUp = state 
end 

function RADAR:TogglePower()
	self.vars.power = not self.vars.power 

	SendNUIMessage( { _type = "radarPower", state = self:IsPowerOn() } )

	-- Power is now turned on 
	if ( self:IsPowerOn() ) then 
		self:SetPoweringUpState( true )

		Citizen.SetTimeout( 2000, function()
			self:SetPoweringUpState( false )

			SendNUIMessage( { _type = "poweredUp" } )
		end )
	else 
		self:ResetAntenna( "front" )
		self:ResetAntenna( "rear" )
	end
end

function RADAR:IsFastDisplayEnabled()
	return self.vars.settings.fastDisplay
end 

function RADAR:SetSettingValue( setting, value )
	if ( value ~= nil ) then 
		self.vars.settings[setting] = value 

		if ( setting == "same" or setting == "opp" ) then 
			self:UpdateRayEndCoords()
		end 
	end 
end 

function RADAR:GetSettingValue( setting )
	return self.vars.settings[setting]
end

function RADAR:IsEitherAntennaOn()
	return self:IsAntennaTransmitting( "front" ) or self:IsAntennaTransmitting( "rear" )
end 

function RADAR:SendSettingUpdate()
	local antennas = self.vars.antennas 
	local fast = self.vars.settings.fastDisplay

	SendNUIMessage( { _type = "settingUpdate", antennaData = antennas, fast = fast } )
end 


--[[------------------------------------------------------------------------
	Radar menu functions  
------------------------------------------------------------------------]]--
function RADAR:SetMenuState( state )
	if ( self:IsPowerOn() ) then 
		self.vars.menuActive = state

		if ( state ) then 
			self.vars.currentOptionIndex = 1
		end
	end
end 

function RADAR:IsMenuOpen()
	return self.vars.menuActive
end 

function RADAR:ChangeMenuIndex()
	local temp = self.vars.currentOptionIndex + 1

	if ( temp > #self.vars.menuOptions ) then 
		temp = 1 
	end 

	self.vars.currentOptionIndex = temp

	self:SendMenuUpdate()
end 

function RADAR:GetMenuOptionTable()
	return self.vars.menuOptions[self.vars.currentOptionIndex]
end 

function RADAR:SetMenuOptionIndex( index )
	self.vars.menuOptions[self.vars.currentOptionIndex].optionIndex = index
end 

function RADAR:GetMenuOptionValue()
	local opt = self:GetMenuOptionTable()
	local index = opt.optionIndex

	return opt.options[index]
end 

function RADAR:ChangeMenuOption( dir )
	local opt = self:GetMenuOptionTable()
	local index = opt.optionIndex

	if ( dir == "front" ) then 
		index = index + 1
		if ( index > #opt.options ) then index = 1 end 
	elseif ( dir == "rear" ) then 
		index = index - 1
		if ( index < 1 ) then index = #opt.options end 
	end

	self:SetMenuOptionIndex( index )

	self:SetSettingValue( opt.settingText, self:GetMenuOptionValue() )

	self:SendMenuUpdate()
end 

function RADAR:GetMenuOptionDisplayText()
	return self:GetMenuOptionTable().displayText
end 

function RADAR:GetMenuOptionText()
	local opt = self:GetMenuOptionTable()

	return opt.optionsText[opt.optionIndex]
end 

function RADAR:SendMenuUpdate()
	SendNUIMessage( { _type = "menu", text = self:GetMenuOptionDisplayText(), option = self:GetMenuOptionText() } )
end 


--[[------------------------------------------------------------------------
	Radar basics functions  
------------------------------------------------------------------------]]--
function RADAR:GetPatrolSpeed()	
	return self.vars.patrolSpeed
end 

function RADAR:GetVehiclePool()
	return self.vars.vehiclePool
end 

function RADAR:GetMaxCheckDist()
	return self.vars.maxCheckDist
end 

function RADAR:GetActiveVehicles()
	return self.vars.activeVehicles
end 

function RADAR:GetStrongestSortFunc()
	return self.sorting.strongest 
end 

function RADAR:GetFastestSortFunc()
	return self.sorting.fastest
end 

function RADAR:SetPatrolSpeed( speed )
	if ( type( speed ) == "number" ) then 
		self.vars.patrolSpeed = self:GetVehSpeedFormatted( speed )
	end
end

function RADAR:SetVehiclePool( pool )
	if ( type( pool ) == "table" ) then 
		self.vars.vehiclePool = pool 
	end
end 

function RADAR:SetActiveVehicles( vehs )
	if ( type( vehs ) == "table" ) then 
		self.vars.activeVehicles = vehs
	end 
end 


--[[------------------------------------------------------------------------
	Radar ray trace functions 
------------------------------------------------------------------------]]--
function RADAR:GetRayTraceState()
	return self.vars.rayTraceState
end

function RADAR:CacheNumRays()
	self.vars.numberOfRays = #self.rayTraces
end 

function RADAR:GetNumOfRays()
	return self.vars.numberOfRays
end

function RADAR:IncreaseRayTraceState()
	self.vars.rayTraceState = self.vars.rayTraceState + 1
end 

function RADAR:ResetRayTraceState()
	self.vars.rayTraceState = 0
end 

function RADAR:GetIntersectedVehIsFrontOrRear( t )
	if ( t > 10.0 ) then 
		return 1 -- vehicle is in front 
	elseif ( t < -10.0 ) then 
		return -1 -- vehicle is behind
	end 

	return 0 -- vehicle is next to self
end 

function RADAR:GetLineHitsSphereAndDir( centre, radius, rayStart, rayEnd )
	-- First we get the normalised ray, this way we then know the direction the ray is going 
	local rayNorm = norm( rayEnd - rayStart )

	-- Then we calculate the ray from the start point to the centre position of the sphere
	local rayToCentre = centre - rayStart

	-- Now that we have the ray to the centre of the sphere, and the normalised ray direction, we 
	-- can calculate the shortest point from the centre of the sphere onto the ray itself. This 
	-- would then give us the opposite side of the right angled triangle. All of the resulting 
	-- values are also in squared form, as performing square root functions is slower. 
	local tProj = dot( rayToCentre, rayNorm )
	local oppLenSqr = dot( rayToCentre, rayToCentre ) - ( tProj * tProj )

	-- Square the radius
	local radiusSqr = radius * radius 

	local rayDist = #( rayEnd - rayStart )
	local distToCentre = #( rayStart - centre ) - ( radius * 2 )

	-- Now all we have to do is compare the squared opposite length and the radius squared, this 
	-- will then tell us if the ray intersects with the sphere.
	-- if ( oppLenSqr < radiusSqr and ( t0 < ( dist + radius ) ) ) then 
	if ( oppLenSqr < radiusSqr and not ( distToCentre > rayDist ) ) then 
		return true, self:GetIntersectedVehIsFrontOrRear( tProj )
	end

	return false, nil 
end 

function RADAR:ShootCustomRay( plyVeh, veh, s, e )
	local pos = GetEntityCoords( veh )
	local dist = #( pos - s )

	local key = tostring( veh )

	if ( DoesEntityExist( veh ) and veh ~= plyVeh and dist < self:GetMaxCheckDist() and not self:HasVehicleAlreadyBeenHit( key ) ) then 
		local entSpeed = GetEntitySpeed( veh )
		local visible = HasEntityClearLosToEntity( plyVeh, veh, 15 ) -- 13 seems okay, 15 too (doesn't grab ents through ents)

		if ( entSpeed > 0.1 and visible ) then 
			local radius, size = self:GetDynamicRadius( veh )

			local hit, relPos = self:GetLineHitsSphereAndDir( pos, radius, s, e )

			if ( hit ) then 
				-- UTIL:DrawDebugSphere( pos.x, pos.y, pos.z, radius, { 255, 0, 0, 40 } )

				self:SetVehicleHasBeenHit( key )

				return true, relPos, dist, entSpeed, size
			end 
		end
	end 

	return false, nil, nil, nil, nil
end 

function RADAR:GetVehsHitByRay( ownVeh, vehs, s, e )
	local t = {}
	local hasData = false 

	for _, veh in pairs( vehs ) do 
		local hit, relativePos, distance, speed, size = self:ShootCustomRay( ownVeh, veh, s, e )

		if ( hit ) then 
			local d = {}
			d.veh = veh 
			d.relPos = relativePos
			-- d.dist = UTIL:Round( distance, 2 ) -- Possibly remove 
			-- d.speed = UTIL:Round( speed, 3 )
			d.dist = distance
			d.speed = speed
			d.size = size

			table.insert( t, d )

			hasData = true 
		end 
	end 

	if ( hasData ) then return t end
end 

function RADAR:CreateRayThread( vehs, from, startX, endX, endY, rayType )
	local startPoint = GetOffsetFromEntityInWorldCoords( from, startX, 0.0, 0.0 )
	local endPoint = GetOffsetFromEntityInWorldCoords( from, endX, endY, 0.0 )

	local hitVehs = self:GetVehsHitByRay( from, vehs, startPoint, endPoint )

	self:InsertCapturedVehicleData( hitVehs, rayType )

	self:IncreaseRayTraceState()
end 

function RADAR:CreateRayThreads( ownVeh, vehicles )
	for _, v in pairs( self.rayTraces ) do 
		self:CreateRayThread( vehicles, ownVeh, v.startVec.x, v.endVec.x, v.endVec.y, v.rayType )
	end 
end 

function RADAR:UpdateRayEndCoords()
	for k, v in pairs( self.rayTraces ) do 
		local endY = self:GetSettingValue( v.rayType ) * v.endVec.baseY
		v.endVec.y = endY
	end 	
end 


--[[------------------------------------------------------------------------
	Radar stage functions 
------------------------------------------------------------------------]]--
function RADAR:GetRadarStage()
	return self.vars.radarStage
end

function RADAR:IncreaseRadarStage()
	self.vars.radarStage = self.vars.radarStage + 1
end 

function RADAR:ResetRadarStage()
	self.vars.radarStage = 0
end


--[[------------------------------------------------------------------------
	Radar antenna functions 
------------------------------------------------------------------------]]--
function RADAR:ToggleAntenna( ant, cb )
	if ( self:IsPowerOn() ) then 
		self.vars.antennas[ant].xmit = not self.vars.antennas[ant].xmit 

		if ( cb ) then cb() end 
	end 
end 

function RADAR:IsAntennaTransmitting( ant )
	return self.vars.antennas[ant].xmit 
end 

function RADAR:GetAntennaTextFromNum( relPos )
	if ( relPos == 1 ) then 
		return "front"
	elseif ( relPos == -1 ) then 
		return "rear"
	end 
end 

function RADAR:GetAntennaMode( ant )
	return self.vars.antennas[ant].mode 
end 

function RADAR:SetAntennaMode( ant, mode, cb )
	if ( type( mode ) == "number" ) then 
		if ( mode >= 0 and mode <= 3 and self:IsPowerOn() ) then 
			self.vars.antennas[ant].mode = mode 

			if ( cb ) then cb() end 
		end 
	end 
end 

function RADAR:SetAntennaSpeed( ant, speed ) 
	if ( type( speed ) == "number" ) then 
		if ( speed >= 0 and speed <= 999 ) then 
			self.vars.antennas[ant].speed = speed
		end 
	end 
end 

function RADAR:SetAntennaDir( ant, dir )
	if ( type( dir ) == "number" ) then 
		if ( dir == 0 or dir == 1 ) then 
			self.vars.antennas[ant].dir = dir 
		end 
	end 
end 

function RADAR:SetAntennaFastMode( ant, mode )
	if ( type( mode ) == "number" ) then 
		if ( mode == 1 or mode == 2 ) then 
			self.vars.antennas[ant].fastMode = mode 
		end 
	end 
end 

function RADAR:SetAntennaFastSpeed( ant, speed ) 
	if ( type( speed ) == "number" ) then 
		if ( speed >= 0 and speed <= 999 ) then 
			self.vars.antennas[ant].fastSpeed = speed
		end 
	end 
end 

function RADAR:SetAntennaFastDir( ant, dir )
	if ( type( dir ) == "number" ) then 
		if ( dir == 0 or dir == 1 ) then 
			self.vars.antennas[ant].fastDir = dir 
		end 
	end 
end 

function RADAR:SetAntennaFastLock( ant, state )
	if ( type( state ) == "boolean" ) then 
		self.vars.antennas[ant].fastLocked = state 
	end 
end 

function RADAR:ResetAntenna( ant )
	-- Overwrite default behaviour, this is because when the system is turned off, the temporary memory is
	-- technically reset, as the setter functions require either the radar power to be on or the antenna to 
	-- be transmitting, this is the only way to reset the values
	self.vars.antennas[ant].xmit = false 
	self.vars.antennas[ant].mode = 0
end 


--[[------------------------------------------------------------------------
	Radar captured vehicle functions 
------------------------------------------------------------------------]]--
function RADAR:GetCapturedVehicles()
	return self.vars.capturedVehicles
end

function RADAR:ResetCapturedVehicles()
	self.vars.capturedVehicles = {}
end

function RADAR:InsertCapturedVehicleData( t, rt )
	if ( type( t ) == "table" and not UTIL:IsTableEmpty( t ) ) then 
		for _, v in pairs( t ) do
			v.rayType = rt 
			table.insert( self.vars.capturedVehicles, v )
		end
	end 
end 

function RADAR:HasVehicleAlreadyBeenHit( key )
	return self.vars.tempVehicleIDs[key] == true 
end 

function RADAR:SetVehicleHasBeenHit( key )
	self.vars.tempVehicleIDs[key] = true 
end 

function RADAR:ResetTempVehicleIDs()
	self.vars.tempVehicleIDs = {}
end 


--[[------------------------------------------------------------------------
	Radar dynamic sphere radius functions 
------------------------------------------------------------------------]]--
function RADAR:GetDynamicDataValue( key )
	return self.vars.sphereSizes[key]
end 

function RADAR:DoesDynamicRadiusDataExist( key )
	return self:GetDynamicDataValue( key ) ~= nil 
end

function RADAR:SetDynamicRadiusKey( key, t )
	self.vars.sphereSizes[key] = t
end 

function RADAR:InsertDynamicRadiusData( key, radius, actualSize )
	if ( self:GetDynamicDataValue( key ) == nil ) then 
		local t = {}

		t.radius = radius 
		t.actualSize = actualSize

		self:SetDynamicRadiusKey( key, t )
	end 
end 

function RADAR:GetRadiusData( key )
	return self.vars.sphereSizes[key].radius or 5.0, self.vars.sphereSizes[key].actualSize
end 

function RADAR:GetDynamicRadius( veh )
	local mdl = GetEntityModel( veh )
	local key = tostring( mdl )
	local dataExists = self:DoesDynamicRadiusDataExist( key )
	
	if ( not dataExists ) then 
		local min, max = GetModelDimensions( mdl )
		local size = max - min 
		local numericSize = size.x + size.y + size.z 
		local dynamicRadius = UTIL:Clamp( ( numericSize * numericSize ) / 12, 4.0, 10.0 )

		self:InsertDynamicRadiusData( key, dynamicRadius, numericSize )

		return dynamicRadius, numericSize
	end 

	return self:GetRadiusData( key )
end


--[[------------------------------------------------------------------------
	Radar functions 
------------------------------------------------------------------------]]--
function RADAR:GetVehSpeedFormatted( speed )
	if ( self.vars.speedType == "mph" ) then 
		return UTIL:Round( math.ceil( speed * 2.236936 ), 0 )
	else 
		return UTIL:Round( math.ceil( speed * 3.6 ), 0 )
	end 
end 
 
function RADAR:GetAllVehicles()
	local t = {}

	for v in UTIL:EnumerateVehicles() do
		table.insert( t, v )
	end 

	return t
end 

function RADAR:CheckVehicleDataFitsMode( ant, rt )
	local mode = self:GetAntennaMode( ant )

	if ( ( mode == 3 ) or ( mode == 1 and rt == "same" ) or ( mode == 2 and rt == "opp" ) ) then return true end 

	return false  
end

function RADAR:GetVehiclesForAntenna()
	local vehs = { ["front"] = {}, ["rear"] = {} }
	local results = { ["front"] = { nil, nil }, ["rear"] = { nil, nil } }

	-- Loop through and split up the vehicles based on front and rear, this is simply because the actual system 
	-- that gets all of the vehicles hit by the radar only has a relative position of either 1 or -1, which we 
	-- then convert below into an antenna string!
	for ant in UTIL:Values( { "front", "rear" } ) do 
		if ( self:IsAntennaTransmitting( ant ) ) then 
			for k, v in pairs( self:GetCapturedVehicles() ) do 
				local antText = self:GetAntennaTextFromNum( v.relPos )

				if ( ant == antText ) then 
					table.insert( vehs[ant], v )
				end 
			end 

			-- As the radar is based on how the real Stalker DSR 2X works, we now sort the dataset by
			-- the 'strongest' (largest) target, this way the first result for the front and rear data
			-- will be the one that gets displayed in the target boxes.
			table.sort( vehs[ant], self:GetStrongestSortFunc() )
		end
	end 

	for ant in UTIL:Values( { "front", "rear" } ) do 
		if ( not UTIL:IsTableEmpty( vehs[ant] ) ) then
			-- Get the 'strongest' vehicle for the antenna 
			for k, v in pairs( vehs[ant] ) do 
				if ( self:CheckVehicleDataFitsMode( ant, v.rayType ) ) then 
					results[ant][1] = v
					break
				end 
			end 

			if ( self:IsFastDisplayEnabled() ) then 
				-- Get the 'fastest' vehicle for the antenna 
				table.sort( vehs[ant], self:GetFastestSortFunc() )

				local temp = results[ant][1]

				for k, v in pairs( vehs[ant] ) do 
					if ( self:CheckVehicleDataFitsMode( ant, v.rayType ) and v.veh ~= temp.veh and v.size < temp.size and v.speed > temp.speed ) then 
						results[ant][2] = v 
						break
					end 
				end 
			end
		end 
	end

	return { ["front"] = { results["front"][1], results["front"][2] }, ["rear"] = { results["rear"][1], results["rear"][2] } }
end 

-- Num4 = 108 - INPUT_VEH_FLY_ROLL_LEFT_ONLY
-- Num5 = 112 - INPUT_VEH_FLY_PITCH_DOWN_ONLY
-- Num6 = 109 - INPUT_VEH_FLY_ROLL_RIGHT_ONLY
-- Num7 = 117 - INPUT_VEH_FLY_SELECT_TARGET_LEFT
-- Num8 = 111 - INPUT_VEH_FLY_PITCH_UP_ONLY
-- Num9 = 118 - INPUT_VEH_FLY_SELECT_TARGET_RIGHT
-- F5 = 166 - INPUT_SELECT_CHARACTER_MICHAEL
function RADAR:RunControlManager()
	-- 'Z' key, toggles debug mode 
	if ( IsDisabledControlJustPressed( 1, 20 ) ) then 
		self.config.debug_mode = not self.config.debug_mode
	end
	
	if ( IsDisabledControlJustPressed( 1, 166 ) ) then 
		SendNUIMessage( { _type = "openRemote" } )
		SetNuiFocus( true, true )
	end 

	if ( IsDisabledControlJustPressed( 1, 117 ) ) then 
		self:TogglePower()
		UTIL:Notify( "Radar power toggled." )
	end 

	if ( IsDisabledControlJustPressed( 1, 118 ) ) then 
		self:ToggleFastDisplay()
		UTIL:Notify( "Fast display toggled." )
	end 

	-- 'Num8' key, toggles front antenna
	--[[ if ( IsDisabledControlJustPressed( 1, 111 ) ) then 
		self:ToggleAntenna( "front" )
		UTIL:Notify( "Front antenna toggled." )
	end 

	-- 'Num5' key, toggles rear antenna
	if ( IsDisabledControlJustPressed( 1, 112 ) ) then 
		self:ToggleAntenna( "rear" )
		UTIL:Notify( "Rear antenna toggled." )
	end ]]
end 


--[[------------------------------------------------------------------------
	NUI callback
------------------------------------------------------------------------]]--
RegisterNUICallback( "togglePower", function()
	RADAR:TogglePower()
end )

RegisterNUICallback( "closeRemote", function()
	SetNuiFocus( false, false )
end )

RegisterNUICallback( "setAntennaMode", function( data ) 
	if ( RADAR:IsPowerOn() and RADAR:IsMenuOpen() ) then 
		RADAR:SetMenuState( false )
		RADAR:SendSettingUpdate()
	else
		RADAR:SetAntennaMode( data.value, tonumber( data.mode ), function()
			SendNUIMessage( { _type = "antennaMode", ant = data.value, mode = tonumber( data.mode ) } )
		end )
	end 

	SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } )
end )

RegisterNUICallback( "toggleAntenna", function( data ) 
	if ( RADAR:IsPowerOn() and RADAR:IsMenuOpen() ) then 
		RADAR:ChangeMenuOption( data.value )
		SendNUIMessage( { _type = "audio", name = "beep" } )
	else
		RADAR:ToggleAntenna( data.value, function()
			SendNUIMessage( { _type = "antennaXmit", ant = data.value, on = RADAR:IsAntennaTransmitting( data.value ) } )
			SendNUIMessage( { _type = "audio", name = RADAR:IsAntennaTransmitting( data.value ) and "xmit_on" or "xmit_off", vol = RADAR:GetSettingValue( "beep" ) } )
		end )
	end 
end )

RegisterNUICallback( "menu", function()
	if ( RADAR:IsMenuOpen() ) then 
		RADAR:ChangeMenuIndex()
	else 
		-- Set the menu state to open, which will prevent anything else from working
		RADAR:SetMenuState( true )
		RADAR:SendMenuUpdate()
	end

	SendNUIMessage( { _type = "audio", name = "beep", vol = RADAR:GetSettingValue( "beep" ) } )
end )


--[[------------------------------------------------------------------------
	Main function  
------------------------------------------------------------------------]]--
function RADAR:Main()
	-- Check to make sure the player is in the driver's seat, and also that the vehicle has a class of VC_EMERGENCY (18)
	if ( DoesEntityExist( PLY.veh ) and PLY.inDriverSeat and PLY.vehClassValid and self:IsPowerOn() and not self:IsPoweringUp() and not self:IsMenuOpen() ) then 
		local plyVehPos = GetEntityCoords( PLY.veh )

		-- First stage of the radar - get all of the vehicles hit by the radar
		if ( self:GetRadarStage() == 0 ) then 
			if ( self:GetRayTraceState() == 0 ) then 
				local vehs = self:GetVehiclePool()

				self:ResetCapturedVehicles()
				self:ResetRayTraceState()
				self:CreateRayThreads( PLY.veh, vehs )
			elseif ( self:GetRayTraceState() == self:GetNumOfRays() ) then 
				self:IncreaseRadarStage()
			end 
		elseif ( self:GetRadarStage() == 1 ) then 
			local data = {} 

			-- Get the player's vehicle speed
			local entSpeed = GetEntitySpeed( PLY.veh )

			if ( entSpeed == 0 ) then 
				data.patrolSpeed = "¦[]"
			else 
				local speed = self:GetVehSpeedFormatted( entSpeed )
				data.patrolSpeed = UTIL:FormatSpeed( speed )
			end 

			-- Only grab data to send if there have actually been vehicles captured by the radar
			if ( not UTIL:IsTableEmpty( self:GetCapturedVehicles() ) ) then 
				local vehsForDisplay = self:GetVehiclesForAntenna()

				self:SetActiveVehicles( vehsForDisplay ) -- not really any point in setting this 
			else
				self:SetActiveVehicles( { ["front"] = { nil, nil }, ["rear"] = { nil, nil } } )
			end

			-- Work out what has to be sent 
			local av = self:GetActiveVehicles()
			data.antennas = { ["front"] = nil, ["rear"] = nil }

			for ant in UTIL:Values( { "front", "rear" } ) do 
				if ( self:IsAntennaTransmitting( ant ) ) then
					data.antennas[ant] = {}

					for i = 1, 2 do 
						data.antennas[ant][i] = { speed = "¦¦¦", dir = 0 }

						-- The vehicle data exists for this slot 
						if ( av[ant][i] ~= nil ) then 
							-- We already have the vehicle speed as we needed it earlier on for filtering 
							data.antennas[ant][i].speed = UTIL:FormatSpeed( self:GetVehSpeedFormatted( av[ant][i].speed ) )

							-- Work out if the vehicle is closing or away 
							local ownH = GetEntityHeading( PLY.veh )
							local tarH = GetEntityHeading( av[ant][i].veh )
							data.antennas[ant][i].dir = UTIL:GetEntityRelativeDirection( ownH, tarH, 120 )
						end 
					end 
				end 
			end 

			-- Send the update to the NUI side
			SendNUIMessage( { _type = "update", speed = data.patrolSpeed, antennas = data.antennas } )

			self:ResetTempVehicleIDs()
			self:ResetRadarStage()
			self:ResetRayTraceState()
		end 
	end 
end 

-- Updates the local player information 
Citizen.CreateThread( function()
	while ( true ) do 
		PLY.ped = PlayerPedId()
		PLY.veh = GetVehiclePedIsIn( PLY.ped, false )
		PLY.inDriverSeat = GetPedInVehicleSeat( PLY.veh, -1 ) == PLY.ped 
		PLY.vehClassValid = GetVehicleClass( PLY.veh ) == 18

		Citizen.Wait( 500 )
	end 
end )

-- Update the vehicle pool every 3 seconds
Citizen.CreateThread( function() 
	while ( true ) do
		local vehs = RADAR:GetAllVehicles()

		RADAR:SetVehiclePool( vehs )

		Citizen.Wait( 3000 )
	end 
end )

-- Main thread
Citizen.CreateThread( function()
	SetNuiFocus( false, false )

	RADAR:CacheNumRays()
	RADAR:UpdateRayEndCoords()

	while ( true ) do
		RADAR:Main()

		Citizen.Wait( 50 )
	end
end )

-- Control manager 
Citizen.CreateThread( function()
	while ( true ) do 
		RADAR:RunControlManager()

		Citizen.Wait( 0 )
	end 
end )




------------------------------ DEBUG ------------------------------
Citizen.CreateThread( function()
	while ( true ) do 
		-- Ray line drawing
		-- local veh = GetVehiclePedIsIn( PlayerPedId(), false )

		for k, v in pairs( RADAR.rayTraces ) do 
			local startP = GetOffsetFromEntityInWorldCoords( PLY.veh, v.startVec.x, 0.0, 0.0 )
			local endP = GetOffsetFromEntityInWorldCoords( PLY.veh, v.endVec.x, v.endVec.y, 0.0 )

			UTIL:DrawDebugLine( startP, endP )
		end

		local av = RADAR:GetActiveVehicles()

		for ant in UTIL:Values( { "front", "rear" } ) do 
			for i = 1, 2, 1 do 
				if ( av[ant] ~= nil and av[ant][i] ~= nil ) then 
					local pos = GetEntityCoords( av[ant][i].veh )
					local r = RADAR:GetDynamicRadius( av[ant][i].veh )

					if ( i == 1 ) then 
						UTIL:DrawDebugSphere( pos.x, pos.y, pos.z, r, { 255, 127, 0, 100 } )
					else 
						UTIL:DrawDebugSphere( pos.x, pos.y, pos.z, r, { 255, 0, 0, 100 } )
					end 
				end 
			end
		end 

		Citizen.Wait( 0 )
	end 
end )

--[[ local types = { "FRONT", "FRONT FAST", "REAR", "REAR FAST" }

Citizen.CreateThread( function()
	while ( true ) do
		-- Caught veh debug printing 
		local av = RADAR:GetActiveVehicles()

		DrawRect( 0.500, 0.850, 0.400, 0.220, 0, 0, 0, 150 )

		for i = 1, 4, 1 do 
			UTIL:DrawDebugText( 0.250 + ( 0.100 * i ), 0.750, 0.60, true, types[i] )

			if ( av[i] ~= nil ) then 
				local pos = GetEntityCoords( av[i].veh )
				local speed = RADAR:GetVehSpeedFormatted( GetEntitySpeed( av[i].veh ) )
				local veh = av[i].veh
				local rt = av[i].rayType
				local dir = UTIL:GetEntityRelativeDirection( GetEntityHeading( GetVehiclePedIsIn( PlayerPedId(), false ) ), GetEntityHeading( veh ), 100 )
				
				if ( dir == 1 ) then dir = "/\\" elseif ( dir == 2 ) then dir = "\\/" else dir = "none" end

				DrawMarker( 2, pos.x, pos.y, pos.z + 3, 0.0, 0.0, 0.0, 0.0, 180.0, 0.0, 1.0, 1.0, 1.0, 255, 255, 0, 70, false, true, 2, nil, nil, false )

				if ( i % 2 == 0 ) then 
					UTIL:DrawDebugText( 0.250 + ( 0.100 * i ), 0.800, 0.60, true, "Ent: " .. tostring( veh ) .. "\nSpeed: ~r~" .. tostring( speed ) .. "~s~mph" .. "\nRay type: " .. tostring( rt ) .. "\nDir: " .. tostring( dir ) )
				else 
					UTIL:DrawDebugText( 0.250 + ( 0.100 * i ), 0.800, 0.60, true, "Ent: " .. tostring( veh ) .. "\nSpeed: " .. tostring( speed ) .. "mph" .. "\nRay type: " .. tostring( rt ) .. "\nDir: " .. tostring( dir ) )
				end 
			else 
				UTIL:DrawDebugText( 0.250 + ( 0.100 * i ), 0.800, 0.60, true, "Ent: nil" .. "\nSpeed: nil" .. "\nRay type: nil" .. "\nDir: nil" )
			end 
		end

		-- Ray line drawing
		local veh = GetVehiclePedIsIn( PlayerPedId(), false )

		for k, v in pairs( RADAR.rayTraces ) do 
			local startP = GetOffsetFromEntityInWorldCoords( veh, v.startVec.x, 0.0, 0.0 )
			local endP = GetOffsetFromEntityInWorldCoords( veh, v.endVec.x, v.endVec.y, 0.0 )

			UTIL:DrawDebugLine( startP, endP )
		end 

		Citizen.Wait( 0 )
	end 
end ) ]]

-- Commands for debugging 
RegisterCommand( "rdr", function( src, args, raw )
	if ( args[1] == "setlimit" ) then 
		RADAR:SetFastLimit( tonumber( args[2] ) ) 
	elseif ( args[1] == "setmode" ) then 
		if ( args[2] == "front" or args[2] == "rear" ) then 
			RADAR:SetAntennaMode( args[2], tonumber( args[3] ) )
		end 
	end 
end, false )