if not TowBarMod then TowBarMod = {} end
if not TowBarMod.Utils then TowBarMod.Utils = {} end

TowBarMod.Utils.tempVector1 = Vector3f.new()
TowBarMod.Utils.tempVector2 = Vector3f.new()

---------------------------------------------------------------------------
--- Util functions
---------------------------------------------------------------------------

function TowBarMod.Utils.isTrailer(vehicle)
	return string.match(string.lower(vehicle:getScript():getName()), "trailer")
end

--- Return vehicles from sector that player can tow by tow bar.
function TowBarMod.Utils.getAviableVehicles(mainVehicle, hasTowBar)
	local vehicles = {}
	if not hasTowBar then return vehicles end

	local square = mainVehicle:getSquare()
	if square == nil then return vehicles end

	-- Match vanilla towing search radius.
	for y=square:getY() - 6, square:getY() + 6 do
		for x=square:getX() - 6, square:getX() + 6 do
			local square2 = getCell():getGridSquare(x, y, square:getZ())
			if square2 then
				for i=1, square2:getMovingObjects():size() do
					local obj = square2:getMovingObjects():get(i-1)
					if obj ~= nil
						and instanceof(obj, "BaseVehicle")
						and obj ~= mainVehicle
						and #(TowBarMod.Utils.getHookTypeVariants(mainVehicle, obj, hasTowBar)) ~= 0 then
						table.insert(vehicles, obj)
					end
				end
			end
		end
	end

	return vehicles
end

--- Return a table with towbar-only hook options for vehicles.
function TowBarMod.Utils.getHookTypeVariants(vehicleA, vehicleB, hasTowBar)
	local hookTypeVariants = {}
	if not hasTowBar then return hookTypeVariants end

	if vehicleA:getVehicleTowing() or vehicleA:getVehicleTowedBy()
			or vehicleB:getVehicleTowing() or vehicleB:getVehicleTowedBy() then
		return hookTypeVariants
	end

	-- Keep tow bars for vehicle-to-vehicle towing only.
	if TowBarMod.Utils.isTrailer(vehicleA) or TowBarMod.Utils.isTrailer(vehicleB) then
		return hookTypeVariants
	end

	if vehicleA:canAttachTrailer(vehicleB, "trailerfront", "trailer") then
		local hookType = {}
		hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
		hookType.func = TowBarMod.Hook.attachByTowBarAction
		hookType.towingVehicle = vehicleB
		hookType.towedVehicle = vehicleA
		hookType.textureName = "tow_bar_icon"
		table.insert(hookTypeVariants, hookType)
	elseif vehicleA:canAttachTrailer(vehicleB, "trailer", "trailerfront") then
		local hookType = {}
		hookType.name = getText("UI_Text_Towing_attach") .. "\n" .. ISVehicleMenu.getVehicleDisplayName(vehicleB) .. "\n" .. getText("UI_Text_Towing_byTowBar")
		hookType.func = TowBarMod.Hook.attachByTowBarAction
		hookType.towingVehicle = vehicleA
		hookType.towedVehicle = vehicleB
		hookType.textureName = "tow_bar_icon"
		table.insert(hookTypeVariants, hookType)
	end

	return hookTypeVariants
end

function TowBarMod.Utils.updateAttachmentsForRigidTow(towingVehicle, towedVehicle, attachmentA, attachmentB)
	local towingAttachment = towingVehicle:getScript():getAttachmentById(attachmentA)
	local towedAttachment = towedVehicle:getScript():getAttachmentById(attachmentB)
	if towingAttachment == nil or towedAttachment == nil then return end

	towingAttachment:setUpdateConstraint(false)
	towingAttachment:setZOffset(0)

	towedAttachment:setUpdateConstraint(false)
	towedAttachment:setZOffset(0)

	local offset = towedAttachment:getOffset()
	local zShift = offset:z() > 0 and 1 or -1
	towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
	local towedModData = towedVehicle:getModData()
	towedModData["isChangedTowedAttachment"] = true
	towedModData["towBarChangedAttachmentId"] = attachmentB
	towedModData["towBarChangedOffsetZShift"] = zShift
	towedVehicle:transmitModData()
end

function TowBarMod.Utils.updateAttachmentsOnDefaultValues(towingVehicle, towedVehicle)
	local towingAttachmentId = towingVehicle:getTowAttachmentSelf()
	local towingAttachment = towingVehicle:getScript():getAttachmentById(towingAttachmentId)
	if towingAttachment ~= nil then
		towingAttachment:setUpdateConstraint(true)
		local zOffset = (towingAttachmentId == "trailer") and -1 or 1
		towingAttachment:setZOffset(zOffset)
	end

	local towedModData = towedVehicle:getModData()
	local changedAttachmentId = towedModData["towBarChangedAttachmentId"] or towedVehicle:getTowAttachmentSelf()
	local towedAttachment = towedVehicle:getScript():getAttachmentById(changedAttachmentId)
	if towedAttachment ~= nil then
		towedAttachment:setUpdateConstraint(true)
		local zOffset = (changedAttachmentId == "trailer") and -1 or 1
		towedAttachment:setZOffset(zOffset)

		if towedModData["isChangedTowedAttachment"] then
			local offset = towedAttachment:getOffset()
			local storedShift = tonumber(towedModData["towBarChangedOffsetZShift"])
			if storedShift ~= nil then
				towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() - storedShift)
			else
				local zShift = offset:z() > 0 and -1 or 1
				towedAttachment:getOffset():set(offset:x(), offset:y(), offset:z() + zShift)
			end
		end
	end

	towedModData["isChangedTowedAttachment"] = false
	towedModData["towBarChangedAttachmentId"] = nil
	towedModData["towBarChangedOffsetZShift"] = nil
	towedVehicle:transmitModData()
end

-----------------------------------------------------------

--- Fix mods that add vehicles without tow attachments
local function fixTowAttachmentsForOtherVehicleMods()
	local scriptManager = getScriptManager()
	local vehicleScripts = scriptManager:getAllVehicleScripts()

	for i = 0, vehicleScripts:size()-1 do
		local script = vehicleScripts:get(i)
		local wheelCount = script:getWheelCount()

		local attachHeigtOffset = -0.5
		if wheelCount > 0 then
			attachHeigtOffset = script:getWheel(0):getOffset():y() + 0.1
		end

		if not string.match(string.lower(script:getName()), "trailer") then
			local trailerAttachment = script:getAttachmentById("trailer")
			if trailerAttachment == nil then
				local attach = ModelAttachment.new("trailer")
				attach:getOffset():set(0, attachHeigtOffset, -script:getPhysicsChassisShape():z()/2 - 0.1)
				attach:setZOffset(-1)
				script:addAttachment(attach)
			end

			local trailerFrontAttachment = script:getAttachmentById("trailerfront")
			if trailerFrontAttachment == nil then
				local attach = ModelAttachment.new("trailerfront")
				attach:getOffset():set(0, attachHeigtOffset, script:getPhysicsChassisShape():z()/2 + 0.1)
				attach:setZOffset(1)
				script:addAttachment(attach)
			end
		end
	end
end

Events.OnGameBoot.Add(fixTowAttachmentsForOtherVehicleMods)

