---------------------------------------------------------------------------
--
-- Helper functions used to compute geometries.
--
-- When this module refer to a geometry table, this assume a table with at least
-- an *x*, *y*, *width* and *height* keys and numeric values.
--
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008 Julien Danjou
-- @utillib gears.geometry
---------------------------------------------------------------------------
local math = math

local gears = {geometry = {rectangle = {} } }

--- Get the square distance between a rectangle and a point.
-- @tparam table geom A rectangle
-- @tparam number geom.x The horizontal coordinate
-- @tparam number geom.y The vertical coordinate
-- @tparam number geom.width The rectangle width
-- @tparam number geom.height The rectangle height
-- @tparam number x X coordinate of point
-- @tparam number y Y coordinate of point
-- @treturn number The squared distance of the rectangle to the provided point.
-- @staticfct gears.geometry.rectangle.get_square_distance
function gears.geometry.rectangle.get_square_distance(geom, x, y)
    local dist_x, dist_y = 0, 0
    if x < geom.x then
        dist_x = geom.x - x
    elseif x >= geom.x + geom.width then
        dist_x = x - geom.x - geom.width + 1
    end
    if y < geom.y then
        dist_y = geom.y - y
    elseif y >= geom.y + geom.height then
        dist_y = y - geom.y - geom.height + 1
    end
    return dist_x * dist_x + dist_y * dist_y
end

--- Return the closest rectangle from `list` for a given point.
-- @tparam table list A list of geometry tables.
-- @tparam number x The x coordinate
-- @tparam number y The y coordinate
-- @return The key from the closest geometry.
-- @staticfct gears.geometry.rectangle.get_closest_by_coord
function gears.geometry.rectangle.get_closest_by_coord(list, x, y)
    local dist = math.huge
    local ret = nil

    for k, v in pairs(list) do
        local d = gears.geometry.rectangle.get_square_distance(v, x, y)
        if d < dist then
            ret, dist = k, d
        end
    end

    return ret
end

--- Return the rectangle containing the [x, y] point.
--
-- Note that if multiple element from the geometry list contains the point, the
-- returned result is nondeterministic.
--
-- @tparam table list A list of geometry tables.
-- @tparam number x The x coordinate
-- @tparam number y The y coordinate
-- @return The key from the closest geometry. In case no result is found, *nil*
--  is returned.
-- @staticfct gears.geometry.rectangle.get_by_coord
function gears.geometry.rectangle.get_by_coord(list, x, y)
    for k, geometry in pairs(list) do
        if x >= geometry.x and x < geometry.x + geometry.width
           and y >= geometry.y and y < geometry.y + geometry.height then
            return k
        end
    end
end

--- Return true whether rectangle B is in the right direction
-- compared to rectangle A.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table gA The geometric specification for rectangle A.
-- @tparam table gB The geometric specification for rectangle B.
-- @return True if B is in the direction of A.
local function is_in_direction(dir, gA, gB)
    if dir == "up" then
        return gA.y > gB.y
    elseif dir == "down" then
        return gA.y < gB.y
    elseif dir == "left" then
        return gA.x > gB.x
    elseif dir == "right" then
        return gA.x < gB.x
    end
    return false
end

--- Calculate distance between two points.
-- i.e: if we want to move to the right, we will take the right border
-- of the currently focused screen and the left side of the checked screen.
--
-- The valid `dir` are:
--
-- * up
-- * down
-- * left
-- * right
--
-- @tparam string dir The direction.
-- @tparam table gA The first rectangle.
-- @tparam table gB The second rectangle.
-- @return The distance between the screens.
local function calculate_distance(dir, gA, gB)
    local gAx = gA.x
    local gAy = gA.y
    local gBx = gB.x
    local gBy = gB.y

    if dir == "up" then
        gBy = gB.y + gB.height
    elseif dir == "down" then
        gAy = gA.y + gA.height
    elseif dir == "left" then
        gBx = gB.x + gB.width
    elseif dir == "right" then
        gAx = gA.x + gA.width
    end

    return math.sqrt((gBx - gAx) ^ 2 + (gBy - gAy) ^ 2)
end

--- Get the nearest rectangle in the given direction. Every rectangle is specified as a table
-- with *x*, *y*, *width*, *height* keys, the same as client or screen geometries.
-- @tparam string dir The direction, can be either *up*, *down*, *left* or *right*.
-- @tparam table recttbl A table of rectangle specifications.
-- @tparam table cur The current rectangle.
-- @return The index for the rectangle in recttbl closer to cur in the given direction. nil if none found.
-- @staticfct gears.geometry.rectangle.get_in_direction
function gears.geometry.rectangle.get_in_direction(dir, recttbl, cur)
    local dist, dist_min
    local target = nil

    -- We check each object
    for i, rect in pairs(recttbl) do
        -- Check geometry to see if object is located in the right direction.
        if is_in_direction(dir, cur, rect) then
            -- Calculate distance between current and checked object.
            dist = calculate_distance(dir, cur, rect)

            -- If distance is shorter then keep the object.
            if not target or dist < dist_min then
                target = i
                dist_min = dist
            end
        end
    end
    return target
end

--- Return true if the area are exactly identical.
--
-- The areas are table with a `x`, `y`, `width` and `height` keys.
--
-- @tparam table a The area.
-- @tparam table b The other area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.are_equal
function gears.geometry.rectangle.are_equal(a, b)
    for _, v in ipairs {"x", "y", "width", "height"} do
        if a[v] ~= b[v] then return false end
    end
    return true
end

--- Return if rectangle `a` is within rectangle `b`.
--
-- This includes the edges. 100% of `a` area has to be within `b` for this
-- function to return true. If you wish to know if any part of `a` intersect
-- with `b`, use `gears.geometry.rectangle.get_intersection`.
--
-- @tparam table a The smaller area.
-- @tparam table b The larger area.
-- @treturn boolean If the areas are identical.
-- @staticfct gears.geometry.rectangle.is_inside
-- @see gears.geometry.rectangle.get_intersection
function gears.geometry.rectangle.is_inside(a, b)
    return (a.x >= b.x
        and a.y >= b.y
        and a.x+a.width  <= b.x + b.width
        and a.y+a.height <= b.y + b.height
    )
end

--- Check if an area intersect another area.
-- @tparam table a The area.
-- @tparam table b The other area.
-- @return True if they intersect, false otherwise.
-- @staticfct gears.geometry.rectangle.area_intersect_area
function gears.geometry.rectangle.area_intersect_area(a, b)
    return (b.x < a.x + a.width
            and b.x + b.width > a.x
            and b.y < a.y + a.height
            and b.y + b.height > a.y)
end

--- Get the intersect area between a and b.
-- @tparam table a The area.
-- @tparam number a.x The horizontal coordinate
-- @tparam number a.y The vertical coordinate
-- @tparam number a.width The rectangle width
-- @tparam number a.height The rectangle height
-- @tparam table b The other area.
-- @tparam number b.x The horizontal coordinate
-- @tparam number b.y The vertical coordinate
-- @tparam number b.width The rectangle width
-- @tparam number b.height The rectangle height
-- @treturn table The intersect area.
-- @staticfct gears.geometry.rectangle.get_intersection
-- @see gears.geometry.rectangle.is_inside
function gears.geometry.rectangle.get_intersection(a, b)
    local g = {}
    g.x = math.max(a.x, b.x)
    g.y = math.max(a.y, b.y)
    g.width = math.min(a.x + a.width, b.x + b.width) - g.x
    g.height = math.min(a.y + a.height, b.y + b.height) - g.y
    if g.width <= 0 or g.height <= 0 then
        g.width, g.height = 0, 0
    end
    return g
end

--- Remove an area from a list, splitting the space between several area that
-- can overlap.
-- @tparam table areas Table of areas.
-- @tparam table elem Area to remove.
-- @tparam number elem.x The horizontal coordinate
-- @tparam number elem.y The vertical coordinate
-- @tparam number elem.width The rectangle width
-- @tparam number elem.height The rectangle height
-- @return The new area list.
-- @staticfct gears.geometry.rectangle.area_remove
function gears.geometry.rectangle.area_remove(areas, elem)
    for i = #areas, 1, -1 do
        -- Check if the 'elem' intersect
        if gears.geometry.rectangle.area_intersect_area(areas[i], elem) then
            -- It does? remove it
            local r = table.remove(areas, i)
            local inter = gears.geometry.rectangle.get_intersection(r, elem)

            if inter.x > r.x then
                table.insert(areas, {
                    x = r.x,
                    y = r.y,
                    width = inter.x - r.x,
                    height = r.height
                })
            end

            if inter.y > r.y then
                table.insert(areas, {
                    x = r.x,
                    y = r.y,
                    width = r.width,
                    height = inter.y - r.y
                })
            end

            if inter.x + inter.width < r.x + r.width then
                table.insert(areas, {
                    x = inter.x + inter.width,
                    y = r.y,
                    width = (r.x + r.width) - (inter.x + inter.width),
                    height = r.height
                })
            end

            if inter.y + inter.height < r.y + r.height then
                table.insert(areas, {
                    x = r.x,
                    y = inter.y + inter.height,
                    width = r.width,
                    height = (r.y + r.height) - (inter.y + inter.height)
                })
            end
        end
    end

    return areas
end

return gears.geometry

-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
