local thisResource = getThisResource()
local thisResourceRoot = getResourceRootElement(thisResource)
local g_suspendedCamera = {}
local g_screenX, g_screenY = guiGetScreenSize ()

local CAMERA_MODE = 1
local CURSOR_MODE = 2
local g_mode
local MOUSE_SUBMODE = 1
local KEYBOARD_SUBMODE = 2
local g_submode = MOUSE_SUBMODE
local g_maxSelectDistance = 155 --units
local g_moveDistance = 100 --move_cursor/freecam

g_workingInterior = 0

local g_suspended = false
local g_enableWorld = true
local g_sensitivityMode = false
local g_showingProperties = false

local g_selectedElement
local g_selectedPart
local g_targetedElement
local g_targetedPart

local g_colless = {}

local g_arrowMarkerSizeRatio = 0.4 --Multiplied by the element's radius
local g_arrowMarkerMinSize = 0.5 --The Absolute minimum size the marker can be
local g_arrowMarker
local g_editorBlip

local CROSSHAIR_NOTHING = 1
local CROSSHAIR_WORLD = 2
local CROSSHAIR_WATER = 3
local CROSSHAIR_MOUSEOVER = 4
local CROSSHAIR_SENSITIVE = 5
local g_crosshairState
local g_showLabels = true
local g_showCrosshair = true

local g_mouseOver = true

local DISTANCE_DECIMAL_PLACES = 3
local INFO_COLOR = -3618561
local INFO_SCALE,INFO_FONT = 1,"default"
local START_X, START_Y, START_Z = 2483, -1666, 21
local START_LOOKX, START_LOOKY, START_LOOKZ = 2483, -1566, 21

local g_dragPosition = { }
local g_dragElement
local g_dragTimer
local DRAG_MINIMUM_CLICK_TIME = 300
local DRAG_CURSOR_MINIMAL_DISTANCE = 5
local DRAG_CAMERA_MINIMAL_DISTANCE = 2

local g_lastClick = { tick=getTickCount() }
local DOUBLE_CLICK_MAX_DELAY = 500 -- In ticks

setCloudsEnabled(false) -- We don't need clouds.
setAmbientSoundEnabled("gunfire", false) -- We don't need random gun shots.

-- PRIVATE
local isColless = {
	marker = true,
	vehicle = true,
	pickup = true,
	ped = true,
	water = true,
}
local hasCol = {
	object = true,
}
local noInteriors = {
	marker = true,
	pickup = true,
	water = true,
}
local attachable = {
	marker = true,
	vehicle = true,
	pickup = true,
	object = true,
	ped = true,
	water = true,
}

local hitComponents = function (hit)
	if hit then return hit.x, hit.y, hit.z
	else return nil end
end

local commonIntersection = function (startX, startY, startZ, endX, endY, endZ, centerX, centerY, centerZ, element)
	local _start = Vector3D:new(startX, startY, startZ)
	local _end = Vector3D:new(endX, endY, endZ)
	local _center = Vector3D:new(centerX, centerY, centerZ)

	local elementType = getElementType(element)
	local radius
	if (elementType == "pickup") then
		radius = 1
	elseif (elementType == "marker") then
		radius = getMarkerSize(element)
	else
		radius = getElementRadius(element)
	end
	return hitComponents(collisionTest.Sphere(_start, _end, _center, radius))
end

local specialIntersections = {
	marker = function (startX, startY, startZ, endX, endY, endZ, centerX, centerY, centerZ, element)
		local markerType = getMarkerType(element)
		local _start = Vector3D:new(startX, startY, startZ)
		local _end = Vector3D:new(endX, endY, endZ)
		local _center = Vector3D:new(centerX, centerY, centerZ)
		local size = getMarkerSize(element)

		if markerType == "corona" or markerType == "ring" or markerType == "arrow" then
			return hitComponents(collisionTest.Sphere(_start, _end, _center, size))
		elseif markerType == "cylinder" then
			return hitComponents(collisionTest.Cylinder(_start, _end, _center, size, size))
		elseif markerType == "checkpoint" then
			return hitComponents(collisionTest.Cylinder(_start, _end, _center, size, 60 * size))
		end
	end,
	water = function (startX, startY, startZ, endX, endY, endZ, centerX, centerY, centerZ, element)
		local _start = Vector3D:new(startX, startY, startZ)
		local _end = Vector3D:new(endX, endY, endZ)
		local _center = Vector3D:new(centerX, centerY, centerZ)
		--
		local x1 = getWaterVertexPosition ( element, 1 )
		local x2,y1 = getWaterVertexPosition ( element, 2 )
		--
		sizeX = x2 - x1
		--
		local _,y2 = getWaterVertexPosition ( element, 3 )
		sizeY = y2 - y1
		
		return hitComponents(collisionTest.Rectangle(_start, _end, _center, sizeX, sizeY))
	end,
}

function startWhenLoaded()
	engineSetAsynchronousLoading ( false, true )
	if getElementData(thisResourceRoot,"g_in_test") then 
		setElementData ( localPlayer, "waitingToStart", true, false )
		return 
	else
		setElementData ( localPlayer, "waitingToStart", nil, false )
	end
	if isInterfaceLoaded() then
		removeEventHandler("onClientResourceStart", root, startWhenLoaded)
		startEditor()
	end
end
addEventHandler("onClientResourceStart", root, startWhenLoaded)

addEventHandler("doSelectElement", root,
	function (submode,shortcut)
		submode = submode or 1
		selectElement(source, submode,shortcut)
	end
)

addEventHandler("onClientRender", root,
	function ()
		if g_suspended or getElementData(thisResourceRoot,"g_in_test") then
			return
		end
		--
		local camX, camY, camZ, endX, endY, endZ = getCameraLine()
		local labelCenterX,labelCenterY,crosshairState
		if g_mode == CAMERA_MODE then
			crosshairState = setCrosshairState
			labelCenterX,labelCenterY = g_screenX/2, g_screenY/2
		else
			if editor_gui.guiGetMouseOverElement() then return end
			labelCenterX,labelCenterY, endX,endY,endZ = getCursorPosition()		
			if not labelCenterX or labelCenterX == 0 or labelCenterX == 1 or labelCenterY == 0 or labelCenterY == 1 then
				return
			end
			labelCenterX = labelCenterX * g_screenX
			labelCenterY = labelCenterY * g_screenY
			crosshairState = setCursorCrosshairState
		end
		if not g_mouseOver or isMTAWindowActive() or guiGetInputEnabled() then
			if g_targetedElement then
				g_targetedPart = nil
				g_targetedElement = nil
				setCrosshairState(CROSSHAIR_NOTHING)
			end
			return
		end
		if g_dragElement and not g_selectedElement then
			local camX, camY, camZ, lookX, lookY, lookZ = getCameraMatrix()
			local distance = math.sqrt((g_dragPosition.x - lookX)^2 +
									   (g_dragPosition.y - lookY)^2 +
				 (g_dragPosition.z and (g_dragPosition.z - lookZ)^2 or 0))

			if distance > DRAG_CAMERA_MINIMAL_DISTANCE then
				selectElement(g_dragElement, MOUSE_SUBMODE)
			end
			return
		end

		local targetElement, targetX, targetY, targetZ, buildingInfo = getTargetedElement()
		if targetElement and not g_SelectWorldBuildingMode_main then
			if targetElement ~= g_targetedPart then	
				g_targetedPart = targetElement
				g_targetedElement = edf.edfGetAncestor(targetElement)
			end
						
			local camX, camY, camZ = getCameraMatrix()
			local distance = math.sqrt( (targetX - camX)^2 + (targetY - camY)^2 + (targetZ - camZ)^2 )
			local roundedDistance = string.format("%." .. (DISTANCE_DECIMAL_PLACES) .. "f", distance)
			createHighlighterText ( labelCenterX,labelCenterY,
							getElementID(g_targetedElement) or "",
							"["..getElementType(g_targetedElement).."]",
							roundedDistance .. " m" 
			)
		else
			if g_targetedElement then
				g_targetedPart = nil
				g_targetedElement = nil
			end
		end

        -- HUD when selecting a world building
		g_worldBuildingInfo = nil
        if g_SelectWorldBuildingMode_main then
            local line1 = ( not g_RemoveWorldBuildingMode_main and "[SELECT WORLD OBJECT]" or "[REMOVE WORLD OBJECT]" )
            local line2 = ""
            local line3 = ""
     		if not targetElement and buildingInfo then
    			local camX, camY, camZ = getCameraMatrix()
    			local distance = math.sqrt( (targetX - camX)^2 + (targetY - camY)^2 + (targetZ - camZ)^2 )
    			local roundedDistance = string.format("%." .. (DISTANCE_DECIMAL_PLACES) .. "f", distance)
    			local modelName = tostring( engineGetModelNameFromID( buildingInfo.id ) )
				if ( buildingInfo.LODid ~= nil ) then
    				line1 = buildingInfo.id .. " (" .. modelName .. ")" .. " LOD: " .. buildingInfo.LODid
				else
    				line1 = buildingInfo.id .. " (" .. modelName .. ")"
				end
				line2 = "[world]"
				line3 = roundedDistance .. " m"
    			g_worldBuildingInfo = buildingInfo
    		end
			createHighlighterText ( labelCenterX,labelCenterY, line1, line2, line3 )
        end

		if g_sensitivityMode then
			crosshairState(CROSSHAIR_SENSITIVE)
			return
		end
		
		if targetElement and not g_selectedElement then
			crosshairState(CROSSHAIR_MOUSEOVER)
		else
			local waterCollision, wCX, wCY, wCZ = testLineAgainstWater(camX, camY, camZ, endX, endY, endZ)
			local groundCollision, gCX, gCY, gCZ = 
				processLineOfSight(camX, camY, camZ, endX, endY, endZ, true, true, true, true, true, true, false, true, localPlayer)
	
			if waterCollision and groundCollision then
				if getDistanceBetweenPoints3D(wCX, wCY, wCZ, camX, camY, camZ) <= getDistanceBetweenPoints3D(gCX, gCY, gCZ, camX, camY, camZ) then
					crosshairState(CROSSHAIR_WATER)
				else
					crosshairState(CROSSHAIR_WORLD)
				end
			elseif waterCollision then
				crosshairState(CROSSHAIR_WATER)
			elseif groundCollision then
				crosshairState(CROSSHAIR_WORLD)
			else
				crosshairState(CROSSHAIR_NOTHING)
			end
		end
	end
)

addEventHandler("onClientElementStreamIn", root,
	function ()
		if isColless[getElementType(source)] then
			g_colless[source] = true
		end
	end
)

addEventHandler("onClientElementStreamOut", root,
	function ()
		if isColless[getElementType(source)] then
			g_colless[source] = nil
		end
	end
)

function startEditor()
	setWorkingInterior(0)

	attachPlayers(true)
	--disable these for now
	-- disableGameHUD()
	-- setElementAlpha(localPlayer, 0)
	--showChat(false)  Uncomment this line for release
	
	
	-- set up camera and submodes
	createCrosshair()
	setCameraMatrix(START_X, START_Y, START_Z,START_LOOKX, START_LOOKY, START_LOOKZ)
	setMode(CAMERA_MODE)
	
	
	fadeCamera(true, 2)
	
	bindInput()
	
	-- loop through collisionless elements, get a table of streamed ones, and freeze vehicles
	for eType in pairs(isColless) do
		for k, element in pairs(getElementsByType(eType)) do
			if eType == "vehicle" then
				makeVehicleStatic(element)
			elseif eType == "ped" then
				makePedStatic ( element )
			end
			if isElementStreamedIn(element) then
				g_colless[element] = true
			end
		end
	end
end

function stopEditor()
	attachPlayers(false)
	engineSetAsynchronousLoading ( true, false )
end
addEventHandler("onClientResourceStop", thisResourceRoot, stopEditor)

function toggleMode(key, keyState)
	if (g_mode == CAMERA_MODE) then
		setMode(CURSOR_MODE)
	else
		setMode(CAMERA_MODE)
	end
end

function keyboardUndo()
	if getKeyState("lctrl") or getKeyState("rctrl") then
		return triggerServerEvent("doUndo", localPlayer)
	end
end

function keyboardRedo()
	if getKeyState("lctrl") or getKeyState("rctrl") then
		return triggerServerEvent("doRedo", localPlayer)
	end
end

function pickupSelectedElement()
	if ( g_submode == KEYBOARD_SUBMODE ) then
		local element = g_selectedElement
		selectElement(element,MOUSE_SUBMODE)
	end
end

function processLineForElements(startX, startY, startZ, endX, endY, endZ)
	local foundElement = false
	local hitX, hitY, hitZ = nil, nil, nil
	--limit foundElementDistance to distance to endpoint because the collision check goes past the endpoint for some reason
	local foundElementDistance = math.sqrt((endX-startX)^2 + (endY-startY)^2 + (endZ-startZ)^2)
	for v in pairs(g_colless) do
		while true do
			--forget elements that have been destroyed
			if not isElement(v) then
				g_colless[v] = nil
				break
			end
			--ignore if it's in a different dimension
			if getElementDimension(v) ~= getWorkingDimension() then
				break
			end
			
			--ignore if its not a valid type
			local strType = getElementType(v)
			if not isColless[strType] and strType ~= "object" then
				g_colless[v] = nil
				break
			end

			--ignore if it's in a different interior
			if not noInteriors[strType] and getElementInterior(v) ~= g_workingInterior then
				break
			end
			--ignore selection arrow marker
			if v == g_arrowMarker then
				break
			end
			local centerX, centerY, centerZ = getElementPosition(v)
			local distance = math.sqrt((centerX-startX)^2 + (centerY-startY)^2 + (centerZ-startZ)^2)

			if (distance <= foundElementDistance) then
				local intersection = specialIntersections[getElementType(v)] or commonIntersection
				local _hitX, _hitY, _hitZ = intersection(startX, startY, startZ, endX, endY, endZ, centerX, centerY, centerZ, v)

				if _hitX then
					foundElement = v
					foundElementDistance = distance
					hitX = _hitX
					hitY = _hitY
					hitZ = _hitZ
				end
			end
			break
		end
	end
	return foundElement, hitX, hitY, hitZ
end

-- selects element mouse is pointing to, or drops selected element (freecam mode)
function processFreecamClick(key, keyState)
	if g_mode ~= CAMERA_MODE then
		return
	end
	local drop
	if (not g_suspended) then
		local clickedElement, targetX, targetY, targetZ = getTargetedElement()

		local camX, camY, camZ, lookX, lookY, lookZ = getCameraMatrix()
		local distance = math.sqrt((targetX - camX)^2 + (targetY - camY)^2 + (targetZ - camZ)^2)

		if (distance > g_maxSelectDistance) then
			clickedElement = nil
			outputDebugString("Cannot select out of range element: " .. getElementType(clickedElement))
			outputDebugString(" distance: " .. distance)
		end
		processClick ( clickedElement, key, keyState, lookX, lookY, lookZ )
	end
end

function processClick ( clickedElement, key, keyState, lookX, lookY, lookZ )

    -- Hook for select a world building
	if handleWorldBuildingMode( keyState ) then
		return
	end

	if not isElement(clickedElement) then clickedElement = nil end
	if keyState == "down" then
		if ((getTickCount() - DOUBLE_CLICK_MAX_DELAY) <= g_lastClick.tick) 
			and g_lastClick.key == key 
			and g_lastClick.element == clickedElement then
			return processDoubleClick ( clickedElement, key )
		end
		g_lastClick = { key = key, element = clickedElement, tick = getTickCount()  }
		if clickedElement then
			g_dragTimer = setTimer ( function()
					g_dragPosition.x = lookX
					g_dragPosition.y = lookY
					g_dragPosition.z = lookZ
					g_dragElement = clickedElement
				end,
				DRAG_MINIMUM_CLICK_TIME,
				1
				)
		else
			g_dragElement = nil
		end
		return
	end
	for k,timer in ipairs(getTimers()) do
		if timer == g_dragTimer then
			killTimer ( timer )
			g_dragTimer = nil
			break
		end
	end

	-- attach element
	if (clickedElement) then
		if (g_selectedElement) then
			g_dragElement = nil
			if g_submode == MOUSE_SUBMODE then
				dropElement(true,true) --Drop it, and stop here
				return
			end
		end
		
		if (key == "select_target_mouse") then
			selectElement(clickedElement, MOUSE_SUBMODE, false, g_selectedElement, g_selectedElement)
		elseif (key == "select_target_keyboard") then
			selectElement(clickedElement, KEYBOARD_SUBMODE, false, g_selectedElement, g_selectedElement)
		end
	elseif (g_selectedElement) then
		if g_submode == MOUSE_SUBMODE then
			g_dragElement = nil
			if (key == "select_target_mouse") then
				dropElement(true,true)
			elseif (key == "select_target_keyboard") then
				local reselect = g_selectedElement
				selectElement(reselect, KEYBOARD_SUBMODE, false, true, true)
			end
		else
			dropElement(true,true)
			g_dragElement = nil
		end
	end
end

function processDoubleClick ( clickedElement, key )
	if not clickedElement then return end
	if triggerEvent ( "onClientElementDoubleClick", clickedElement, key ) then
		if key == "select_target_keyboard" then
			if ( selectElement(clickedElement, KEYBOARD_SUBMODE) ) then
				editor_gui.openPropertiesBox(g_selectedElement)
			end
		end
	end
end

function getTargetedElement(hitX, hitY, hitZ)
	local targetX, targetY, targetZ, targetedElement

	if (g_mode == CAMERA_MODE) then
		targetX, targetY, targetZ, targetedElement, buildingInfo = processCameraLineOfSight()
	elseif (g_mode == CURSOR_MODE) then
		targetX, targetY, targetZ, targetedElement, buildingInfo = processCursorLineOfSight()
	end

	local camX, camY, camZ = getCameraMatrix()
	
	-- check for collisionless elements between camera and collision point
	local tempElement, tempHitX, tempHitY, tempHitZ = 
		processLineForElements(camX, camY, camZ, targetX, targetY, targetZ)

	-- if collisionless element was found in front of the collision point, use it
	if (tempElement) then
		targetedElement = tempElement
		targetX, targetY, targetZ = tempHitX, tempHitY, tempHitZ
	end
	
	if targetedElement then
		if getElementType(targetedElement) == "player" then
			targetedElement = false
		end
	end
	
	return targetedElement, targetX, targetY, targetZ, buildingInfo
end

function createCrosshair()
	g_showLabels = true
	setCrosshairState(CROSSHAIR_NOTHING)
end

function createHighlighterText ( absX,absY,text1,text2,text3 )
	if not g_showLabels then return false end
	local height = dxGetFontHeight ( INFO_SCALE, INFO_FONT )
	--
	local extent = dxGetTextWidth ( text1, INFO_SCALE, INFO_FONT )
	local xPos = absX - (extent/2)
	local yPos = absY - 48
	--First one is for the shaddow
	dxDrawText ( text1, xPos + 1, yPos + 1, xPos + extent, yPos + height, -16777216, INFO_SCALE, INFO_FONT )
	dxDrawText ( text1, xPos, yPos, xPos + extent, yPos + height, INFO_COLOR, INFO_SCALE, INFO_FONT )
	--
	extent = dxGetTextWidth ( text2, INFO_SCALE, INFO_FONT )
	xPos = absX - (extent/2)
	yPos = absY - 32
	dxDrawText ( text2, xPos + 1, yPos + 1, xPos + extent, yPos + height, -1728053248, INFO_SCALE, INFO_FONT )
	dxDrawText ( text2, xPos, yPos, xPos + extent, yPos + height, -1714894593, INFO_SCALE, INFO_FONT )
	--
	extent = dxGetTextWidth ( text3, INFO_SCALE, INFO_FONT )
	xPos = absX - (extent/2)
	yPos = absY + 16
	dxDrawText ( text3, xPos + 1, yPos + 1, xPos + extent, yPos + height, -16777216, INFO_SCALE, INFO_FONT )
	dxDrawText ( text3, xPos, yPos, xPos + extent, yPos + height, INFO_COLOR, INFO_SCALE, INFO_FONT )  
	return true
end


function setCrosshairState(state)
	if not g_showCrosshair then return end
	local color = tocolor(255,255,255,102)
	if state == CROSSHAIR_WORLD then
		color = tocolor(255,255,255,255)
	elseif state == CROSSHAIR_WATER then
		color = tocolor(80,203,227,255)
	elseif state == CROSSHAIR_MOUSEOVER then
		color = tocolor(191,203,134,255)
	elseif state == CROSSHAIR_SENSITIVE then
		color = tocolor(255,0,0,255)
	end
	dxDrawImage ( g_screenX/2 - 16, g_screenY/2 - 16, 32, 32, "client/images/crosshair.png",0,0,0,color,false)
	g_crosshairState = state
end

function setCursorCrosshairState(state)
	-- if not g_showCrosshair then return end
	if state == CROSSHAIR_NOTHING or state == CROSSHAIR_WORLD then
		return
	elseif state == CROSSHAIR_WATER then
		color = tocolor(80,203,227,255)
	elseif state == CROSSHAIR_MOUSEOVER then
		color = tocolor(191,203,134,255)
	elseif state == CROSSHAIR_SENSITIVE then
		color = tocolor(255,0,0,255)
	end
	local x,y = getCursorPosition()
	dxDrawImage ( g_screenX*x - 2, g_screenY*y - 1, 15, 15,  "client/images/cursor.png", 0,0,0,color,true )
	-- g_crosshairState = state
end

function makeVehicleStatic(vehicle)
	vehicle = vehicle or source
	setVehicleDamageProof(vehicle, true)
	setElementFrozen(vehicle, true)
	setElementCollisionsEnabled(vehicle, false)
end
addEventHandler("doSetVehicleStatic", getRootElement(), makeVehicleStatic)

function makePedStatic(ped)
	ped = ped or source
	setElementCollisionsEnabled ( ped, false )
end
addEventHandler("doSetPedStatic", getRootElement(), makePedStatic)

function setRepresentationCollisionsEnabled(element, state)
	for k, child in ipairs(getElementChildren(element)) do
		if edf.edfGetParent(child) == element then
			if hasCol[getElementType(child)] then
				setElementCollisionsEnabled(child, state)
			end
			setRepresentationCollisionsEnabled(child, state)
		end
	end
end

-- Drag and drop
function processCursorMove(cursorX, cursorY, absoluteX, absoluteY, worldX, worldY, worldZ)
	if g_dragElement then
		local distance = math.sqrt((absoluteX - g_dragPosition.x)^2 + (absoluteY - g_dragPosition.y)^2)
		if distance > DRAG_CURSOR_MINIMAL_DISTANCE
		then
			selectElement(g_dragElement, MOUSE_SUBMODE)
			g_dragPosition = { }
			g_dragElement = nil
		end
	end
end

-- selects element mouse is pointing to, or drops selected element (cursor mode)
function processCursorClick(button, keyState,cursorX, cursorY, worldX, worldY, worldZ, clickedElement)
	local clickedGUI
	if editor_gui then
		clickedGUI = editor_gui.guiGetMouseOverElement()
	end
	
	local key = getControlFromMouseKey(mouseButtonToKeyName(button))
	if ( clickedGUI ) or g_suspended or not g_enableWorld or not key then
        maybeCancelWorldBuildingMode(keyState)
		return
	end

	local targetElement, targetX, targetY, targetZ = getTargetedElement(worldX, worldY, worldZ)
	clickedElement = targetElement or clickedElement

	if clickedElement then
		-- get start point of vector
		local camX, camY, camZ = getCameraMatrix()
		-- get end point of vector
		local hitX, hitY, hitZ = targetX, targetY, targetZ--worldX, worldY, worldZ
		local distance = math.sqrt((hitX - camX)^2 + (hitY - camY)^2 + (hitZ - camZ)^2)
		if (distance > g_maxSelectDistance) then
			outputDebugString("Cannot select out of range element: " .. getElementType(clickedElement) .. " at distance: " .. distance)
			clickedElement = nil
		end
	end
	processClick ( clickedElement, key, keyState, cursorX, cursorY )
end

function getCameraLine()
	-- get start point of vector
	 camX, camY, camZ, endX, endY, endZ = getCameraMatrix()
	 
	-- alter the vector length to fit the maximum distance
	local distance = getDistanceBetweenPoints3D ( camX, camY, camZ, endX, endY, endZ )
	targetX = camX + ((endX - camX)/distance) * g_maxSelectDistance
	targetY = camY + ((endY - camY)/distance) * g_maxSelectDistance
	targetZ = camZ + ((endZ - camZ)/distance) * g_maxSelectDistance	
	
	return camX, camY, camZ, endX, endY, endZ
end

function mouseButtonToKeyName( buttonName )
	if buttonName == "left" then
		return "mouse1"
	elseif buttonName == "right" then
		return "mouse2"
	elseif buttonName == "middle" then
		return "mouse3"
	end
end

function getControlFromMouseKey( mouseKey )
	if mouseKey == cc.select_target_keyboard then return "select_target_keyboard"
	elseif mouseKey == cc.select_target_mouse then return "select_target_mouse"
	end
end

function disableGameHUD()
	showPlayerHudComponent("area_name", false)
	showPlayerHudComponent("armour", false)
	showPlayerHudComponent("breath", false)
	showPlayerHudComponent("clock", false)
	showPlayerHudComponent("health", false)
	showPlayerHudComponent("money", false)
	showPlayerHudComponent("vehicle_name", false)
	showPlayerHudComponent("weapon", false)
end

-- PUBLIC
function selectElement(element, submode, shortcut, dropreleaseLock, dropclonedrop)
	local openProperties
	submode = submode or g_submode
	
	if not isElement(element) then return end
	
	if getElementType(element) == "vehicle" and getVehicleType(element) == "Train" then
		setTrainDerailed(element, true)
	end
	
	if not triggerEvent ( "onClientElementSelect", element, submode, shortcut ) then return false end
	
	if g_selectedElement then
		dropElement(dropreleaseLock, dropclonedrop)
	end
	
	-- check the editing lock
	local locked = getElementData(element, "me:locked")
	if locked and locked ~= localPlayer then
		assert(isElement(locked), "Bad lock owner ["..tostring(locked).."] for element: "..getElementType(element))
		editor_gui.outputMessage("Cannot select element, it is being controlled by " .. getPlayerName(locked), 255,255,255)
		return false
	end
	
	-- check if the element is, or is part of, an EDF element
	element = edf.edfGetAncestor(element)
	local handle  = edf.edfGetHandle(element)
	
	if not handle then
		if attachable[getElementType(element)] then
			handle = element
		else
			handle = nil
		end
	end
	
	assert(handle == nil or isElement(handle), "Bad handle ["..tostring(handle).."] for element: "..getElementType(element))
	
	-- temporarily disable collisions for all parts
	setRepresentationCollisionsEnabled(element, false)
	
	-- if we can position this element, grab it and add the markers
	if handle then
		local move_resource
		if (submode == MOUSE_SUBMODE) then
			if (g_mode == CAMERA_MODE) then
				move_resource = move_freecam
			elseif (g_mode == CURSOR_MODE) then
				move_resource = move_cursor
			end
			move_resource.setMaxMoveDistance(g_moveDistance)
		elseif (submode == KEYBOARD_SUBMODE) then
			move_resource = move_keyboard
		else
			error("Element selection submode is invalid",2)
		end
		
		assert(move_resource.attachElement(handle), "move resource call failed when attaching element: " .. tostring(getElementType(element)))
	end
	
	g_submode = submode
	g_selectedPart = handle
	g_selectedElement = element
	
	if handle then
		--create the editor blip and keyboard arrow pointing to the handle
		g_editorBlip = createBlipAttachedTo(handle, 0, 2, 255, 255, 0, 255)
		if (submode == KEYBOARD_SUBMODE) then
			createArrowMarker(handle)
		end
	else
		editor_gui.openPropertiesBox( element, false, shortcut )
		openProperties = true
	end
	
	triggerServerEvent("doLockElement", element)
	
	-- trigger server selection events
	triggerServerEvent("onElementSelect", element)
	
	--Emulate a fake mouse  move to get the element to position properly
	-- if not openProperties then
		-- local cursorX, cursorY = 0.5,0.5
		-- local absoluteX = math.ceil(g_screenX*cursorX)
		-- local absoluteY = math.ceil(g_screenY*cursorY)		
		-- worldX, worldY, worldZ = getWorldFromScreenPosition ( absoluteX, absoluteY, 100 )
		-- triggerEvent ( "onClientCursorMove", root, cursorX, cursorY, absoluteX, absoluteY, worldX, worldY, worldZ )
	-- end
	
	if ( shortcut ) then
		if not openProperties then 
			editor_gui.openPropertiesBox( element, false, shortcut ) 
		end
	end

	outputConsole("Attached element: " .. getElementType(element))
	return true
end

function dropElement(releaseLock,clonedrop)
	if not g_selectedElement or not isElement(g_selectedElement) then
		return false
	end
	
	-- trigger client selection events
	if not triggerEvent("onClientElementDrop", g_selectedElement) then return false end
	
	if releaseLock ~= false then
		releaseLock = true
	end
	
	-- re-enable collisions for all parts
	setRepresentationCollisionsEnabled(g_selectedElement, true)
	
	if g_selectedPart then
		local move_resource
		if (g_submode == MOUSE_SUBMODE) then
			if (g_mode == CAMERA_MODE) then
				move_resource = move_freecam
			elseif (g_mode == CURSOR_MODE) then
				move_resource = move_cursor
			end
			g_moveDistance = move_resource.getMaxMoveDistance()
		elseif (g_submode == KEYBOARD_SUBMODE) then
			move_resource = move_keyboard
		end
		
		assert(move_resource.detachElement(), "move resource call failed when detaching element: " .. tostring(getElementType(g_selectedElement)))
		
		--destroy the editor blip and keyboard arrow pointing to the handle
		destroyElement(g_editorBlip)
		if (g_submode == KEYBOARD_SUBMODE) then
			showGridlines ( false )
			destroyElement(g_arrowMarker)
			g_arrowMarker = nil
		end
	end

	if releaseLock then
		triggerServerEvent("doUnlockElement", g_selectedElement)
	end

	-- trigger server selection events
	triggerServerEvent("onElementDrop", g_selectedElement)

	local droppedElement = g_selectedElement
	g_selectedElement = false
	g_selectedPart = false
	
	outputConsole("Detached element.")
	
	if getCommandState("clone_drop_modifier") and clonedrop then
		return doCloneElement(droppedElement)
	else
		return true
	end
end

-- sets the camera mode to cursor or freecam
function setMode(newMode)
	if g_suspended then
		return
	end
	
	if newMode == CAMERA_MODE then
		if (g_selectedElement and g_submode == MOUSE_SUBMODE) then
			move_cursor.detachElement()
			g_moveDistance = move_cursor.getMaxMoveDistance()
			showCursor(false)
			move_freecam.attachElement(edf.edfGetHandle(g_selectedElement) or g_selectedElement)
			move_freecam.setMaxMoveDistance(g_moveDistance)
		else
			showCursor(false)
		end
		
		triggerEvent("onFreecamMode", root)
		freecam.setFreecamEnabled(false, false, false, true)
		
		bindControl("select_target_mouse", "both", processFreecamClick)
		bindControl("select_target_keyboard", "both", processFreecamClick)

		if g_mode == CURSOR_MODE then
			removeEventHandler("onClientCursorMove", root, processCursorMove)
			removeEventHandler("onClientClick", root, processCursorClick)
		end
		
		showCrosshair(true,true)
		g_mode = newMode
		
		return true
	elseif newMode == CURSOR_MODE then
		if (g_selectedElement and g_submode == MOUSE_SUBMODE) then
			move_freecam.detachElement()
			g_moveDistance = move_freecam.getMaxMoveDistance()
			showCursor(true)
			move_cursor.attachElement(edf.edfGetHandle(g_selectedElement) or g_selectedElement)
			move_cursor.setMaxMoveDistance(g_moveDistance)
		else
			showCursor(true)
		end
		
		triggerEvent("onCursorMode", root)
		freecam.setFreecamDisabled(true)
		
		addEventHandler("onClientCursorMove", root, processCursorMove)
		addEventHandler("onClientClick", root, processCursorClick)
		
		if g_mode == CAMERA_MODE then
			unbindControl("select_target_mouse", "both", processFreecamClick)
			unbindControl("select_target_keyboard", "both", processFreecamClick)
		end
		
		showCrosshair(false)
		g_mode = newMode
		
		return true
	else
		error("Requested mode is invalid", 2)
	end
end

-- sets the maximum distance at which an element can be selected
function setMaxSelectDistance(distance)
	assert((distance >= 0), "Distance must be a positive number")
	g_maxSelectDistance = distance
	return true
end

function getSelectedElement()
	return g_selectedElement or false
end

function getMode()
	return g_mode
end

function getSubmode()
	return g_submode
end

function getMaxSelectDistance()
	return g_maxSelectDistance
end

function destroySelectedElement()
	if g_selectedElement then
		local element = g_selectedElement
		dropElement(false)
		return triggerServerEvent("doDestroyElement", element)
	end
end

function createElement_cmd(cmd, elementType, resourceName)
	if elementType and resourceName then
		doCreateElement(elementType, resourceName)
	else
		outputConsole("* Syntax: /"..cmd.." <type> <from-definition>")
	end
end

function cloneSelectedElement()
	if g_selectedElement then
		if g_submode == KEYBOARD_SUBMODE then
			editor_gui.outputMessage ( "WARNING: The selected element has been cloned on top of itself.", 255, 130, 0 )
		end
		doCloneElement ( g_selectedElement, g_submode )
	end
end

function showCrosshair(status,labelStatus)
	g_showCrosshair = status
	-- g_showLabels = labelStatus
	return true
end

function getWorkingInterior()
	return g_workingInterior
end

function setWorkingInterior( interior )
	setCameraInterior( interior )
	setElementInterior(getLocalPlayer(), interior)
	g_workingInterior = interior
	return true
end

function suspend(leavePlayersAttached)
	outputConsole("Suspending editor_main.")

	local move_resource
	if (g_submode == MOUSE_SUBMODE) then
		if (g_mode == CAMERA_MODE) then
			move_resource = move_freecam
		elseif (g_mode == CURSOR_MODE) then
			move_resource = move_cursor
		end
	elseif (g_submode == KEYBOARD_SUBMODE) then
		move_resource = move_keyboard
	end

	move_resource.disable()
	unbindInput(true)
	triggerEvent("onEditorSuspended",root)
	editor_gui.setGUIShowing(false)
	showCrosshair(false,false)
	
	if (g_mode == CAMERA_MODE) then
		triggerEvent("doSetFreecamDisabled", root, true)
		unbindControl("select_target_mouse", "both", processFreecamClick)
	elseif (mode == CURSOR_MODE) then
		showCursor(false,false)
	end
	g_suspendedCamera = { getCameraMatrix() }
	if not leavePlayersAttached then
		attachPlayers(false)
	end
	g_suspended = true
end

function resume(dontEnableMove)
	outputConsole("Resuming editor_main.")
	
	local move_resource
	if (g_submode == MOUSE_SUBMODE) then
		if (g_mode == CAMERA_MODE) then
			move_resource = move_freecam
			bindControl("select_target_mouse", "both", processFreecamClick)
			bindControl("select_target_keyboard", "both", processFreecamClick)
		elseif (g_mode == CURSOR_MODE) then
			move_resource = move_cursor
		end
	elseif (g_submode == KEYBOARD_SUBMODE) then
		move_resource = move_keyboard
	end
	
	if not dontEnableMove then
		move_resource.enable()
	end
	bindInput(true)
	triggerEvent("onEditorResumed",root)
	editor_gui.setGUIShowing(true)
	
	setElementInterior ( localPlayer, getWorkingInterior() )
	setCameraInterior ( getWorkingInterior() )
	setCameraMatrix ( unpack(g_suspendedCamera) )
	
	if (g_mode == CAMERA_MODE) then
		triggerEvent("doSetFreecamEnabled", root, false, false, false, true)
		showCrosshair(true,true)
	elseif (g_mode == CURSOR_MODE) then
		showCursor(true)
		showCrosshair(false,true)
	end
	attachPlayers(true)
	g_suspended = false
end

function bindInput(commandsOnly)
	addCommandHandler("clone", cloneSelectedElement)
	addCommandHandler("create", createElement_cmd)
	addCommandHandler("destroy", destroySelectedElement)
	addCommandHandler("delete", destroySelectedElement)
	if ( commandsOnly ) then
		return true
	end
	
	bindControl("toggle_cursor", "down", toggleMode)
	bindControl("pickup_selected_element", "down", pickupSelectedElement)
	bindControl("destroy_selected_element", "down", destroySelectedElement)
	bindControl("clone_selected_element", "down", cloneSelectedElement)
	bindControl("drop_selected_element", "down", dropElement)
	bindControl("undo", "down", keyboardUndo)
	bindControl("redo", "down", keyboardRedo)
	bindControl("high_sensitivity_mode", "down", toggleSensitivityMode)
end

function unbindInput(commandsOnly)
	removeCommandHandler("clone", cloneSelectedElement)
	removeCommandHandler("create", createElement_cmd)
	removeCommandHandler("destroy", destroySelectedElement)
	removeCommandHandler("delete", destroySelectedElement)

	if ( commandsOnly ) then
		return true
	end
	unbindControl("toggle_cursor", "down", toggleMode)
	unbindControl("pickup_selected_element", "down", pickupSelectedElement)
	unbindControl("destroy_selected_element", "down", destroySelectedElement)
	unbindControl("clone_selected_element", "down", cloneSelectedElement)
	unbindControl("drop_selected_element", "down", dropElement)
	unbindControl("undo", "down", keyboardUndo)
	unbindControl("redo", "down", keyboardRedo)
	unbindControl("high_sensitivity_mode", "down", toggleSensitivityMode)
end

-- get the point and element targeted by the camera
function processCameraLineOfSight()
	local camX, camY, camZ, endX, endY, endZ = getCameraLine()
	
	-- get collision point on the line
	local surfaceFound, targetX, targetY, targetZ, targetElement,
            nx, ny, nz, material, lighting, piece,
            buildingId, bx, by, bz, brx, bry, brz, buildingLOD
		= processLineOfSight(camX, camY, camZ, endX, endY, endZ, true, true, true, true, true, true, false, true, localPlayer, true)

	-- if there is none, use the end point of the vector as the collision point
	if not surfaceFound then
	    targetX, targetY, targetZ = endX, endY, endZ
	end
	
	local buildingInfo = buildingId and { LODid=buildingLOD, id=buildingId, x=( bx == 0 and targetX or bx ), y=( by == 0 and targetY or by ), z=( bz == 0 and targetZ or bz ), rx=brx, ry=bry, rz=brz }

	return targetX, targetY, targetZ, targetElement, buildingInfo
end

-- get the point and element targeted by the cursor
function processCursorLineOfSight()
	-- get start point of vector
	local camX, camY, camZ = getCameraMatrix()
	
	--! getCursorPosition is innacurate, so we get the coordinates from the click event
	local cursorX, cursorY, endX, endY, endZ = getCursorPosition()

	local surfaceFound, targetX, targetY, targetZ, targetElement,
            nx, ny, nz, material, lighting, piece,
            buildingId, bx, by, bz, brx, bry, brz, buildingLOD
        = processLineOfSight(camX, camY, camZ, endX, endY, endZ, true, true, true, true, true, true, false, true, localPlayer, true)

	-- if there is none, use the end point of the vector as the collision point
	if not surfaceFound then
	    targetX, targetY, targetZ = endX, endY, endZ
	end

	local buildingInfo = buildingId and { LODid=buildingLOD, id=buildingId, x=( bx == 0 and targetX or bx ), y=( by == 0 and targetY or by ), z=( bz == 0 and targetZ or bz ), rx=brx, ry=bry, rz=brz }
	
	return targetX, targetY, targetZ, targetElement, buildingInfo
end

function setWorldClickEnabled ( bool )
	g_enableWorld = bool
	return true
end

function toggleEditorKeys ( bool )
	if bool then
		bindControl("toggle_cursor", "down", toggleMode) --possibly add a check to check if its already bound
	else
		unbindControl("toggle_cursor", "down", toggleMode) --possibly add a check to check if its already bound
	end
end

function enableMouseOver ( bool )
	g_mouseOver = bool
	return
end

function updateArrowMarker( element )
	if not g_arrowMarker then return end

	local element = element or g_selectedElement
	if not element then return end

	local radius = edf.edfGetElementRadius(element) or 1
	
	local offsetZ = radius + 1
	local markerSize = math.max(g_arrowMarkerMinSize,g_arrowMarkerSizeRatio*radius)
	
	if isElementAttached(g_arrowMarker) then
		detachElements(g_arrowMarker, getElementAttachedTo(g_arrowMarker))
	end
	setMarkerSize(g_arrowMarker, markerSize)
	attachElements(g_arrowMarker, element, 0, 0, offsetZ)
	showGridlines ( element )
end

function createArrowMarker( handle )
	if not handle or not isElement(handle) then return end

	if g_arrowMarker then destroyElement(g_arrowMarker) end

	g_arrowMarker = createMarker(0, 0, 0, "arrow", .5, 255, 255, 0, 255)
	setElementDimension(g_arrowMarker, getWorkingDimension())
	updateArrowMarker( handle )
end

function setMovementType( movementType )
	if g_arrowMarker then
		if movementType == "move" then
			setMarkerColor(g_arrowMarker, 255, 255, 0)
		elseif movementType == "rotate" then
			setMarkerColor(g_arrowMarker, 0, 255, 0)
		end
	end
end

function toggleSensitivityMode ()
	g_sensitivityMode = not g_sensitivityMode
	--Only with objects for now
	for i,element in ipairs(getElementsByType"object") do
		if isElementStreamedIn(element) then
			g_colless[element] = g_sensitivityMode or nil
		end
	end
	if g_sensitivityMode then
		addEventHandler ( "onClientElementStreamIn", root, streamInCollessObjects )
		addEventHandler ( "onClientElementStreamOut", root, streamOutCollessObjects )
	else
		removeEventHandler ( "onClientElementStreamIn", root, streamInCollessObjects )
		removeEventHandler ( "onClientElementStreamOut", root, streamOutCollessObjects )
	end
end

function streamInCollessObjects()
	if getElementType(source) == "object" then
		g_colless[source] = true
	end
end

function streamOutCollessObjects()
	if getElementType(source) == "object" then
		g_colless[source] = nil
	end
end


--
-- World building clone functions
--

-- Catch create event and switch on 'Select World Building Mode'
addEvent ( "onClientElementPreCreate" )
addEventHandler( "onClientElementPreCreate", root,
	function ( elementType, resourceName, creationParameters, attachLater, shortcut )
		if ( shortcut == "selworld" or elementType == "removeWorldObject" ) then
			g_SelectWorldBuildingMode_main = true
			g_RemoveWorldBuildingMode_main = ( elementType == "removeWorldObject" )
			cancelEvent ( )
		end
	end
)

-- Catch click and create copy of a world building
function handleWorldBuildingMode(keyState)
	if ( keyState == "down" ) then
		if g_SelectWorldBuildingMode_main then
			if g_worldBuildingInfo then
				if ( g_RemoveWorldBuildingMode_main ) then
					local tempElement = createObject ( g_worldBuildingInfo.id, g_worldBuildingInfo.x, g_worldBuildingInfo.y, g_worldBuildingInfo.z )
					local radius = ( edf.edfGetElementRadius ( tempElement ) or 15 ) + 2
					destroyElement ( tempElement )
					creationParameters = {}
					creationParameters.model = g_worldBuildingInfo.id
					creationParameters.lodModel = g_worldBuildingInfo.LODid
					creationParameters.radius = radius
					creationParameters.position = { g_worldBuildingInfo.x, g_worldBuildingInfo.y, g_worldBuildingInfo.z }
					creationParameters.interior = g_workingInterior
					triggerServerEvent ( "doCreateElement", localPlayer, "removeWorldObject", "editor_main", creationParameters, false )
				else
					creationParameters = {}
					creationParameters.model = g_worldBuildingInfo.id
					creationParameters.position = { g_worldBuildingInfo.x, g_worldBuildingInfo.y, g_worldBuildingInfo.z }
					creationParameters.rotation = { g_worldBuildingInfo.rx, g_worldBuildingInfo.ry, g_worldBuildingInfo.rz }
					doCreateElement("object", "editor_main", creationParameters)
				end
        	end
			g_SelectWorldBuildingMode_main = false
			g_RemoveWorldBuildingMode_main = false
			return true
		end
	end
	return false
end

-- Catch click and cancel 'Select World Building Mode'
function maybeCancelWorldBuildingMode(keyState)
	if keyState == "down" then
		g_SelectWorldBuildingMode_main = false
		g_RemoveWorldBuildingMode_main = false
	end
end
