-- EFL LuaJIT bindings: Eina (rectangle module)
-- For use with Elua

local ffi = require("ffi")
local C = ffi.C

ffi.cdef [[
    typedef struct _Eina_Rectangle {
        int x, y, w, h;
    } Eina_Rectangle;

    typedef struct _Eina_Rectangle_Pool Eina_Rectangle_Pool;

    typedef unsigned char Eina_Bool;

    Eina_Rectangle_Pool *eina_rectangle_pool_new(int w, int h);
    Eina_Rectangle_Pool *eina_rectangle_pool_get(Eina_Rectangle *rect);
    Eina_Bool            eina_rectangle_pool_geometry_get(Eina_Rectangle_Pool *pool, int *w, int *h);
    void                *eina_rectangle_pool_data_get(Eina_Rectangle_Pool *pool);
    void                 eina_rectangle_pool_data_set(Eina_Rectangle_Pool *pool, const void *data);
    void                 eina_rectangle_pool_free(Eina_Rectangle_Pool *pool);
    int                  eina_rectangle_pool_count(Eina_Rectangle_Pool *pool);
    Eina_Rectangle      *eina_rectangle_pool_request(Eina_Rectangle_Pool *pool, int w, int h);
    void                 eina_rectangle_pool_release(Eina_Rectangle *rect);

    Eina_Rectangle *eina_rectangle_new (int x, int y, int w, int h);
    void            eina_rectangle_free(Eina_Rectangle *rect);
]]

local cutil = require("cutil")
local util  = require("util")

local M = {}

local eina

local init = function()
    eina = util.lib_load("eina")
end

local shutdown = function()
    util.lib_unload("eina")
end

cutil.init_module(init, shutdown)

local spans_intersect = function(c1, l1, c2, l2)
    return not (((c2 + l2) <= c1) or (c2 >= (c1 + l1)))
end

M.spans_intersect = spans_intersect

M.Rectangle = ffi.metatype("Eina_Rectangle", {
    __new = function(self, x, y, w, h)
        return ffi.gc(eina.eina_rectangle_new(x, y, w, h), self.free)
    end,
    __index = {
        free = function(self) eina.eina_rectangle_free(ffi.gc(self, nil)) end,

        get_pool = function(self)
            local  v = eina.eina_rectangle_pool_get(self)
            return v ~= nil and v or nil
        end,

        is_empty = function(self)
            return (self.w < 1) or (self.h < 1)
        end,

        coords_from = function(self, x, y, w, h)
            self.x, self.y, self.w, self.h = x, y, w, h
        end,

        intersects = function(self, other)
            return spans_intersect(self.x, self.w, other.x, other.w)
               and spans_interscet(self.y, self.h, other.y, other.h)
        end,

        xcoord_inside = function(self, x)
            return ((x >= self.x) and (x < (self.x + self.w)))
        end,

        ycoord_inside = function(self, y)
            return ((y >= self.y) and (y < (self.y + self.h)))
        end,

        coords_inside = function(self, x, y)
            return self:xcoord_inside(x) and self:ycoord_inside(y)
        end,

        union = function(dst, src)
            if dst.x > src.x then
                dst.w = dst.w + dst.x - src.x
                dst.x = src.x
            end
            if (dst.x + dst.w) < (src.x + src.w) then
                dst.w = src.x + src.w - dst.x
            end
            if dst.y > src.y then
                dst.h = dst.h + dst.y - src.y
                dst.y = src.y
            end
            if (dst.y + dst.h) < (src.y + src.h) then
                dst.h = src.y + src.h - dst.y
            end
        end,

        intersection = function(dst, src)
            if not dst:intersects(src) then
                return false
            end

            if dst.x < src.x then
                dst.w = dst.w + dst.x - src.x
                dst.x = src.x
                if dst.w < 0 then dst.w = 0 end
            end
            if (dst.x + dst.w) > (src.x + src.w) then
                dst.w = src.x + src.w - dst.x
            end
            if dst.y < src.y then
                dst.h = dst.h + dst.y - src.y
                dst.y = src.y
                if dst.h < 0 then dst.h = 0 end
            end
            if (dst.y + dst.h) > (src.y + src.h) then
                dst.h = src.y + src.h - dst.y
            end

            return true
        end,

        rescale_in = function(self, inr, outr)
            self.x = inr.x - outr.x
            self.y = inr.y - outr.y
            self.w = inr.w
            self.h = inr.h
        end,

        rescale_out = function(self, inr, outr)
            self.x = outr.x + inr.x
            self.y = outr.y + inr.y
            self.w = outr.w
            self.h = outr.h
        end,

        is_valid = function(self)
            return self.w > 0 and self.h > 0
        end,

        max_x = function(self) return self.x + self.w end,
        max_y = function(self) return self.y + self.h end,

        x_cut = function(self, slice, leftover, amount)
            if amount > self.w then return false end
            if  slice then
                slice:coords_from(self.x, self.y, amount, self.h)
            end
            if  leftover then
                leftover:coords_from(self.x + amount, self.y,
                                     self.w - amount, self.h)
            end
            return true
        end,

        y_cut = function(self, slice, leftover, amount)
            if amount > self.h then return false end
            if  slice then
                slice:coords_from(self.x, self.y, self.w, amount)
            end
            if  leftover then
                leftover:coords_from(self.x, self.y + amount,
                                     self.w, self.h - amount)
            end
            return true
        end,

        width_cut = function(self, slice, leftover, amount)
            if (self.w - amount) < 0 then return false end
            if  slice then
                slice:coords_from(self.x + (self.w - amount), self.y,
                                  amount, self.h)
            end
            if  leftover then
                leftover:coords_from(self.x, self.y, self.w - amount, self.h)
            end
            return true
        end,

        height_cut = function(self, slice, leftover, amount)
            if (self.h - amount) < 0 then return false end
            if  slice then
                slice:coords_from(self.x, self.y + (self.h - amount),
                                  self.w, amount)
            end
            if  leftover then
                leftover:coords_from(self.x, self.y, self.w, self.h - amount)
            end
            return true
        end,

        subtract = function(self, other)
            if not self:is_valid() then return false end

            local o1, o2, o3, o4 = ffi.new("Eina_Rectangle"),
                                   ffi.new("Eina_Rectangle"),
                                   ffi.new("Eina_Rectangle"),
                                   ffi.new("Eina_Rectangle")

            local intersection = ffi.new("Eina_Rectangle", self)
            if not intersection:intersection(other) then
                o1:coords_from(self)
                return true, o1, o2, o3, o4
            end

            local leftover = ffi.new("Eina_Rectangle")

            local cut = self.h - (intersection.y - self.y)
            if cut > self.h then cut = self.h end
            self:height_cut(self, leftover, o1, cut)

            local tmp = ffi.new("Eina_Rectangle", leftover)
            if tmp:intersection(intersection) then
                cut = leftover.h - (leftover:max_y() - tmp:max_y())
                if cut > leftover.h then cut = leftover.h end
                leftover:y_cut(leftover, o2, cut)
            end

            local tmp = ffi.new("Eina_Rectangle", leftover)
            if tmp:intersection(intersection) then
                cut = leftover.w - (tmp.x - leftover.x)
                if cut > leftover.w then cut = leftover.w end
                leftover:width_cut(leftover, o3, cut)
            end

            local tmp = ffi.new("Eina_Rectangle", leftover)
            if tmp:intersection(intersection) then
                cut = leftover.w - (leftover:max_x() - tmp:max_x())
                if cut > leftover.w then cut = leftover.w end
                leftover:x_cut(leftover, o4, cut)
            end

            return true, o1, o2, o3, o4
        end
    }
})

M.Rectangle_Pool = ffi.metatype("Eina_Rectangle_Pool", {
    __new = function(self, w, h)
        return ffi.gc(eina.eina_rectangle_pool_new(w, h), self.free)
    end,

    __len = function(self) return eina.eina_rectangle_pool_count(self) end,

    __index = {
        free = function(self)
            eina.eina_rectangle_pool_free(ffi.gc(self, nil))
        end,
        get = function(rect) return rect:get_pool() end,

        geometry_get = function(self)
            local w, h = ffi.new("int[1]"), ffi.new("int[1]")
            eina.eina_rectangle_pool_geometry_get(self, w, h)
            return w[0], h[0]
        end,

        --[[
        data_get = function(self)
            return eina.eina_rectangle_pool_data_get(self)
        end,
        data_set = function(self, v)
            eina.eina_rectangle_pool_data_set(self, v)
        end,
        ]]

        request = function(self, w, h)
            local v = eina.eina_rectangle_pool_request(self, w, h)
            return v ~= nil and v or nil
        end,

        release = function(self, rect)
            eina.eina_rectangle_pool_release(rect)
        end
    }
})

return M
