util.land = {}

util.land.VALID_GROUND_SURFACES = {
    [land.SurfaceType.LAND] = true,
    [land.SurfaceType.ROAD] = true,
    [land.SurfaceType.RUNWAY] = false, -- looks cooler when spawned on airfields
}

-- enum / enum list of all avoided objects
util.land.COLLISION_OBJECTS = Object.Category.SCENERY

-- defined constants used for collision Avoidance
util.land.GOLDEN_ANGLE = math.pi * (3 - math.sqrt(5))
util.land.COLLISION_MAX_UNITS = 1e3

--- returns a close valid (not obstructed) position
---
--- INFO: if the passed position is already valid, it gets returned without changes
---
--- WARNING: This function is only implemented for ground units / objects
--- @param pos table (vec2)the original position you  
--- @param box table (box)dimensions of the unit / object you want to validate (see DCS Unit:getDesc().box)
--- @param rScl? fun(phi:number, r:number, i:number) scales the distance with every step.
--- @param phiScl? fun(r:number, i:number) scales the angle with every step.
--- @return table @(see DCS vec2)
function util.land.getCloseValidPos(pos, box, rScl, phiScl)
    if type(pos) ~= "table" then error(util.error.argErr(pos, "table", 1)) end
    if type(box) ~= "table" then error(util.error.argErr(box, "table", 2)) end

    local vPos = util.land.isValidPos(pos, box)

    -- if the position is already valid return with minor performance impact
    if vPos then return pos end

    -- default set for the funtion phiScl
    if (type(phiScl) ~= "function") then
        phiScl = function(phi, r, i) return i * util.land.GOLDEN_ANGLE end
    end

    -- default set for the funtion rScl
    if (type(rScl) ~= "function") then
        rScl = function(r, i) return r >= 100 and r + 100 or r * 1.5 end
    end

    -- variable init. Includes a deepcopy for the position (getting modified)
    local posCpy = { x = pos.x, y = pos.y }
    local dx, dy = 0, 0
    local phi, r = 0, 5
    local i, valid = 0, false

    --points = {}

    repeat
        -- update angle and radius according to predefined behaviour
        phi, r = phiScl(phi, r, i), rScl(r, i)

        -- calculate deltas and new position
        dx, dy = util.math.polToCar(phi, r)

        posCpy.x, posCpy.y = posCpy.x + dx, posCpy.y + dy

        -- checks if new position is a valid one
        valid = util.land.isValidPos(posCpy, box)
        -- inclement counter (this is used to check for unexpected infinit loops or hard to place areas)
        i = i + 1

        --points[i] = { x = posCpy.x, y = posCpy.y }
    until(valid or i >= util.land.COLLISION_MAX_UNITS)

    -- warn the user the calculation went wrong
    if (i >= util.land.COLLISION_MAX_UNITS) then
        env.warning("Could not find valid position for [" .. tostring(pos.x) ..", " .. tostring(pos.y) .. "]. Retruned original point", true)
    end

    -- mission_debug.placeUnits(points)

    return posCpy
end

--- checks if the spawn postition is valid. This includes a surface check as well as a check for COLLISION_OBJECTS objects.
--- @param pos table position to check (vec2)
--- @param box table dimensions of the unit / object you want to validate (see DCS Unit:getDesc().box)
--- @return boolean
function util.land.isValidPos(pos, box)
    if type(pos) ~= "table" then error(util.error.argErr(pos, "table", 1)) end
    if type(box) ~= "table" then error(util.error.argErr(box, "table", 2)) end

    -- check if the positions surface type is part of the allowed types
    if (not util.land.VALID_GROUND_SURFACES[land.getSurfaceType(pos)]) then
        return false
    end

    -- create a box starting at the position (ground lvl) and expands to the box of the object
    local y = land.getHeight(pos)
    local vol = {
        id = world.VolumeType.BOX,
        params = {
            min = {x = pos.x + box.min.x, y = y + box.min.y, z = pos.y + box.min.z},
            max = {x = pos.x + box.max.x, y = y + box.max.y, z = pos.y + box.max.z},
        }
    }

    -- handler used to check wether an object was found
    -- we expect a rather small amount of hits (more than 1 are really rare) for a unit. If this function gets scaled to bigger objects
    -- with more thant 1 hit, it might makes sence to abort the search process after an object was fond by returning false
    local handler = function (item, val) return true end

    -- if there are any fobidden objects around the position gets invalid => return false
    return world.searchObjects(util.land.COLLISION_OBJECTS, vol, handler) <= 0
end

--- calculates the distance between two points on the map
--- @param pos1 table
--- @param pos2 table
--- @return number distance
function util.land.distanceBetween(pos1, pos2)
    return util.math.vecAbs(util.math.vecSub(pos2, pos1))
end