--[[*]]-- RotatorsLib --[[*]]--

require "rLib.Shared"
require "rSemiTruck/ArmorSync"

rLib.Vehicles = rLib.Vehicles or {}
rLib.Vehicles.Armor =
{
	Callback = {}
}

local ArmorSync = rSemiTruck.ArmorSync

local pendingRepairByPartKey = {}

local function getPartRepairKey(vehicle, part)
	if not vehicle or not part then
		return nil
	end

	local vehicleId = vehicle.getId and vehicle:getId() or nil
	local partId = part.getId and part:getId() or nil
	if vehicleId == nil or not partId then
		return nil
	end

	return tostring(vehicleId) .. ":" .. tostring(partId)
end

local function clearPendingRepair(vehicle, part)
	local key = getPartRepairKey(vehicle, part)
	if key then
		pendingRepairByPartKey[key] = nil
	end
end

local function shouldSendRepair(vehicle, part, targetCondition)
	local key = getPartRepairKey(vehicle, part)
	if not key then
		return true
	end

	local currentCondition = tonumber(part:getCondition()) or 0
	if currentCondition >= targetCondition then
		pendingRepairByPartKey[key] = nil
		return false
	end

	local cached = pendingRepairByPartKey[key]
	if cached
		and cached.currentCondition == currentCondition
		and cached.targetCondition == targetCondition
	then
		return false
	end

	pendingRepairByPartKey[key] = {
		currentCondition = currentCondition,
		targetCondition = targetCondition,
	}

	return true
end

local function dispatchRepair(player, vehicle, part, condition)
	return ArmorSync.dispatchRepair(player, vehicle, part, condition, "W900", "applyArmorRepair")
end

local function ForEachLoadedVehicle(cell, visitor)
	if type(visitor) ~= "function" or not cell or not cell.getVehicles then
		return
	end

	local vehicles = cell:getVehicles()
	if not vehicles then
		return
	end

	if type(vehicles.size) == "function" and type(vehicles.get) == "function" then
		for index = 0, vehicles:size() - 1 do
			visitor(vehicles:get(index), index)
		end
		return
	end

	if type(vehicles.iterator) == "function" then
		local ok, iterator = pcall(function() return vehicles:iterator() end)
		if ok and iterator then
			local index = 0
			while iterator:hasNext() do
				visitor(iterator:next(), index)
				index = index + 1
			end
			return
		end
	end

	local count = nil
	if type(vehicles.size) == "function" then
		local ok, sizeValue = pcall(function() return vehicles:size() end)
		if ok then
			count = sizeValue
		end
	elseif type(vehicles) == "table" then
		count = #vehicles
	end

	if count then
		for index = 0, count - 1 do
			local ok, vehicle = pcall(function() return vehicles[index] end)
			if not ok or vehicle == nil then
				ok, vehicle = pcall(function() return vehicles[index + 1] end)
			end
			if ok and vehicle then
				visitor(vehicle, index)
			end
		end
		return
	end

	if type(vehicles) == "table" then
		for index, vehicle in ipairs(vehicles) do
			visitor(vehicle, index - 1)
		end
	end
end

--

function rLib.Vehicles.Armor.ProcessCell(player)
	assert(rLib.arg(player, "IsoPlayer"))

	local result = false

	ForEachLoadedVehicle(player:getCell(), function(vehicle)
		if vehicle and rLib.Vehicles.Armor.ProcessVehicle(player, vehicle) then
			result = true
		end
	end)

	return result
end

function rLib.Vehicles.Armor.ProcessVehicle(player, vehicle)
	assert(rLib.arg(player, "IsoPlayer"))
	assert(rLib.arg(vehicle, "BaseVehicle"))

	local result = false

	for p=0, vehicle:getPartCount()-1 do
		if rLib.Vehicles.Armor.ProcessPart(player, vehicle, vehicle:getPartByIndex(p)) then
			result = true
		end
	end

	return result
end

function rLib.Vehicles.Armor.ProcessPart(player, vehicle, part)
	assert(rLib.arg(player, "IsoPlayer"))
	assert(rLib.arg(vehicle, "BaseVehicle"))
	assert(rLib.arg(part, "VehiclePart"))
	assert(vehicle == part:getVehicle())

	local armor = part:getTable("armor")
	if type(armor) ~= "table" then
		return false
	end

	-- in case someone wants to use `table armor` for their own armor implementation --
	if not armor.logic or (armor.logic ~= "RotatorsLib" and armor.logic ~= "rLib") then
		return false
	end

	local luaTable = armor.lua and type(armor.lua) == "table"

	local check = part:getInventoryItem()

	if luaTable and armor.lua.check then
		check = rLib.lua(armor.lua.check, player, vehicle, part, armor, "check")
	end

	if not check then
		return false
	end

	if armor.needs and type(armor.needs) == "table" then
		if armor.needs.part then
			local guard = vehicle:getPartById(armor.needs.part)

			if not guard or not guard:getInventoryItem() then
				return false
			end

			if armor.needs.condition then
				local condition = tonumber(armor.needs.condition)
				if not condition or guard:getCondition() < condition then
					return false
				end
			end
		end
	end

	local result = false

	if armor.condition then
		local partCondition = tonumber(armor.condition)
		if not partCondition then
			return false
		elseif part:getCondition() >= partCondition then
			clearPendingRepair(vehicle, part)
		elseif part:getCondition() < partCondition then
			local repair = true

			if luaTable and armor.lua.repair then
				repair = rLib.lua(armor.lua.repair, player, vehicle, part, armor, "repair")
			end

			if repair and shouldSendRepair(vehicle, part, partCondition) then
				rLib.dprint("[rLib.Vehicles.Armor.ProcessPart] repair %d:%s : %d -> %d", vehicle:getId(), part:getId(), part:getCondition(), partCondition)
				dispatchRepair(player, vehicle, part, partCondition)
			end

			result = repair
		end
	end

	return result
end

--

function rLib.Vehicles.Armor.Callback.TrueOnEngine(player, vehicle, part, armor, action)
	assert(rLib.arg(player, "IsoPlayer"))
	assert(rLib.arg(vehicle, "BaseVehicle"))
	assert(rLib.arg(part, "VehiclePart"))
	assert(rLib.arg(armor, "table"))
	assert(rLib.arg(action, "string"))

	return part:getId() == "Engine"
end

function rLib.Vehicles.Armor.Callback.TrueOnZero(player, vehicle, part, armor, action)
	assert(rLib.arg(player, "IsoPlayer"))
	assert(rLib.arg(vehicle, "BaseVehicle"))
	assert(rLib.arg(part, "VehiclePart"))
	assert(rLib.arg(armor, "table"))
	assert(rLib.arg(action, "string"))

	return part:getCondition() <= 0
end

function rLib.Vehicles.Armor.Callback.FalseOnZero(player, vehicle, part, armor, action)
	assert(rLib.arg(player, "IsoPlayer"))
	assert(rLib.arg(vehicle, "BaseVehicle"))
	assert(rLib.arg(part, "VehiclePart"))
	assert(rLib.arg(armor, "table"))
	assert(rLib.arg(action, "string"))

	return part:getCondition() > 0
end

--

local tick = 0

function rLib.Vehicles.Armor.OnPlayerUpdate(player)
	if tick < 250 then
		tick = tick + 1
	else
		tick = 0
	end

	local vehicle = player:getVehicle() or player:getNearVehicle()
	if (vehicle and rLib.Vehicles.Armor.ProcessVehicle(player, vehicle)) or (tick == 0) then
		rLib.Vehicles.Armor.ProcessCell(player)
	end
end

Events.OnPlayerUpdate.Add(rLib.Vehicles.Armor.OnPlayerUpdate)
