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

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

ffi.cdef [[
    typedef enum _Eina_Log_Level {
        EINA_LOG_LEVEL_CRITICAL,
        EINA_LOG_LEVEL_ERR,
        EINA_LOG_LEVEL_WARN,
        EINA_LOG_LEVEL_INFO,
        EINA_LOG_LEVEL_DBG,
        EINA_LOG_LEVELS,
        EINA_LOG_LEVEL_UNKNOWN = (-2147483647 - 1)
    } Eina_Log_Level;

    extern int EINA_LOG_DOMAIN_GLOBAL;

    int  eina_log_domain_register  (const char *name, const char *color);
    void eina_log_domain_unregister(int domain);

    int  eina_log_domain_registered_level_get(int domain);
    void eina_log_domain_registered_level_set(int domain, int level);

    void eina_log_print(int            domain,
                        Eina_Log_Level level,
                        const char    *file,
                        const char    *function,
                        int            line,
                        const char    *fmt,
                        ...);

    typedef struct _Domain_Private {
        int domain;
    } Domain_Private;

    typedef struct _Domain Domain;

    void *malloc(size_t);
    void    free(void*);
]]

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

local M = {}

local eina
local default_domain, global_domain

local init = function()
    eina = util.lib_load("eina")
    global_domain  = ffi.new("Domain_Private", eina.EINA_LOG_DOMAIN_GLOBAL)
    default_domain = global_domain
end

local shutdown = function()
    util.lib_unload("eina")
    default_domain, global_domain = nil, nil
end

cutil.init_module(init, shutdown)

M.level = {
    CRITICAL = C.EINA_LOG_LEVEL_CRITICAL,
    ERR      = C.EINA_LOG_LEVEL_ERR,
    WARN     = C.EINA_LOG_LEVEL_WARN,
    INFO     = C.EINA_LOG_LEVEL_INFO,
    DBG      = C.EINA_LOG_LEVEL_DBG,
    UNKNOWN  = C.EINA_LOG_LEVEL_UNKNOWN,
    levels   = C.EINA_LOG_LEVELS
}

M.color = {
    DEFAULT   = "\x1B[36m",
    LIGHTRED  = "\x1B[31;1m",
    RED       = "\x1B[31m",
    LIGHTBLUE = "\x1B[34;1m",
    BLUE      = "\x1B[34m",
    GREEN     = "\x1B[32;1m",
    YELLOW    = "\x1B[33;1m",
    ORANGE    = "\x1B[0;33m",
    WHITE     = "\x1B[37;1m",
    LIGHTCYAN = "\x1B[36;1m",
    CYAN      = "\x1B[36m",
    RESET     = "\x1B[0m",
    HIGH      = "\x1B[1m"
}

local get_dom = function(dom)
    dom = dom:__get_domain()
    if not dom then return nil end
    return ffi.cast("Domain_Private*", dom).domain
end

local unregister_dom = function(dom)
    local dompri = ffi.cast("Domain_Private*", dom)
    eina.eina_log_domain_unregister(dompri.domain)
    C.free(dompri)
end

local log_full = function(dom, level, file, func, line, msg)
    dom = get_dom(dom)
    if not dom then return end
    eina.eina_log_print(dom, level, file, func, line, msg)
end
M.log_full = log_full

local getinfo = debug.getinfo

local getfuncname = function(info)
    return info.name or "<" .. tostring(info.func) .. ">"
end

local log = function(dom, level, msg, loff)
    local info = getinfo(2 + (loff or 0), "nlSf")
    log_full(dom, level, info.source, getfuncname(info), info.currentline, msg)
end
M.log = log

local logfuncs = {
    { "crit", C.EINA_LOG_LEVEL_CRITICAL },
    { "err",  C.EINA_LOG_LEVEL_ERR      },
    { "warn", C.EINA_LOG_LEVEL_WARN     },
    { "info", C.EINA_LOG_LEVEL_INFO     },
    { "dbg",  C.EINA_LOG_LEVEL_DBG      }
}

for i, v in ipairs(logfuncs) do
    M[v[1]] = function(msg)
        if not default_domain then return end
        local info = getinfo(2, "nlSf")
        local dom = ffi.cast("Domain_Private*", default_domain).domain
        eina.eina_log_print(dom, v[2], info.source, getfuncname(info),
            info.currentline, msg)
    end
end

logfuncs = nil

M.Domain_Base = util.Object:clone {
    set_level = function(self, level)
        local dom = get_dom(self)
        if not dom then return end
        eina.eina_log_domain_registered_level_set(dom, level)
    end,

    get_level = function(self)
        local dom = get_dom(self)
        if not dom then return -1 end
        return eina.eina_log_domain_registered_level_get(dom)
    end,

    __get_domain = function(self)
        return nil
    end,

    log = function(self, level, msg, loff)
        log(self, level, msg, (loff or 0) + 1)
    end,

    is_valid = function(self)
        return self:__get_domain() ~= nil
    end
}

M.Domain_Global = M.Domain_Base:clone {
    __get_domain = function(self)
        return global_domain
    end
}

M.global_domain = M.Domain_Global

M.Domain_Default = M.Domain_Base:clone {
    __get_domain = function(self)
        return default_domain
    end
}

M.default_domain = M.Domain_Default

M.Domain = M.Domain_Base:clone {
    __ctor = function(self, name, color)
        local dom = eina.eina_log_domain_register(name, color or "\x1B[36m")
        if dom < 0 then return end
        local dompri = ffi.cast("Domain_Private*",
            C.malloc(ffi.sizeof("Domain_Private")))
        dompri.domain = dom
        self.__domain = ffi.gc(ffi.cast("Domain*", dompri), unregister_dom)
    end,

    unregister = function(self)
        if not self.__domain then return end
        unregister_dom(ffi.gc(self.__domain, nil))
        self.__domain = nil
    end,

    __get_domain = function(self)
        return self.__domain
    end
}

M.set_default_domain = function(dom)
    dom = dom:__get_domain()
    if not dom then return end
    default_domain = dom
end

return M
