local string = require "string"
local math = require "math"
local Log = require "log"

local mod, floor, ceil, abs, pow = math.fmod, math.floor, math.ceil, math.abs, math.pow
local fmt = string.format

-- removes the decimal part of a number
local function fix(n)
    n = tonumber(n)
    return n and ((n > 0 and floor or ceil)(n))
end

local function lshift(a, b)
    if math.pow then
        return a * math.pow(2, b)
    else
        return a << b
    end
end

--[[ RFC5424 
  0       Emergency: system is unusable
  1       Alert: action must be taken immediately
  2       Critical: critical conditions
  3       Error: error conditions
  4       Warning: warning conditions
  5       Notice: normal but significant condition
  6       Informational: informational messages
  7       Debug: debug-level messages
--]]
local SEVERITY = {
    EMERG = 0,
    ALERT = 1,
    CRIT = 2,
    ERR = 3,
    WARNING = 4,
    NOTICE = 5,
    INFO = 6,
    DEBUG = 7,
}

--[[ RFC5424 
  0             kernel messages
  1             user-level messages
  2             mail system
  3             system daemons
  4             security/authorization messages
  5             messages generated internally by syslogd
  6             line printer subsystem
  7             network news subsystem
  8             UUCP subsystem
  9             clock daemon
 10             security/authorization messages
 11             FTP daemon
 12             NTP subsystem
 13             log audit
 14             log alert
 15             clock daemon (note 2)
 16             local use 0  (local0)
 17             local use 1  (local1)
 18             local use 2  (local2)
 19             local use 3  (local3)
 20             local use 4  (local4)
 21             local use 5  (local5)
 22             local use 6  (local6)
 23             local use 7  (local7)
--]]
local FACILITY = {
    KERN = lshift(0, 3),
    USER = lshift(1, 3),
    MAIL = lshift(2, 3),
    DAEMON = lshift(3, 3),
    AUTH = lshift(4, 3),
    SYSLOG = lshift(5, 3),
    LPR = lshift(6, 3),
    NEWS = lshift(7, 3),
    UUCP = lshift(8, 3),
    CRON = lshift(9, 3),
    CLKD = lshift(9, 3),
    AUTHPRIV = lshift(10, 3),
    FTP = lshift(11, 3),
    NTP = lshift(12, 3),
    SECURITY = lshift(13, 3),
    AUDIT = lshift(13, 3),
    CONSOLE = lshift(14, 3),
    ALERT = lshift(14, 3),
    CLKD2 = lshift(15, 3),
    LOCAL0 = lshift(16, 3),
    LOCAL1 = lshift(17, 3),
    LOCAL2 = lshift(18, 3),
    LOCAL3 = lshift(19, 3),
    LOCAL4 = lshift(20, 3),
    LOCAL5 = lshift(21, 3),
    LOCAL6 = lshift(22, 3),
    LOCAL7 = lshift(23, 3),
}

local LVL2SYSLOG = {
    [Log.LVL.EMERG] = SEVERITY.EMERG,
    [Log.LVL.ALERT] = SEVERITY.ALERT,
    [Log.LVL.FATAL] = SEVERITY.CRIT,
    [Log.LVL.ERROR] = SEVERITY.ERR,
    [Log.LVL.WARNING] = SEVERITY.WARNING,
    [Log.LVL.NOTICE] = SEVERITY.NOTICE,
    [Log.LVL.INFO] = SEVERITY.INFO,
    [Log.LVL.DEBUG] = SEVERITY.DEBUG,
    [Log.LVL.TRACE] = SEVERITY.DEBUG,
}

local function Date2SysLog(now)
    local Y, M, D = now:getdate()
    local h, m, s = now:gettime()

    local b = -now:getbias();
    local x = abs(b);

    return fmt("%.4d-%.2d-%.2dT%.2d:%.2d:%.2d%s%.2d:%.2d", Y, M, D, h, m, s, b < 0 and "-" or "+", fix(x / 60),
        floor(mod(x, 60)))
end

local M = {}

function M.new(facility, host_name, app_name, procid, msgid)
    if not facility then
        facility = FACILITY.USER
    else
        facility = FACILITY[facility:upper()]
    end
    host_name = host_name or '-'
    app_name = app_name or '-'
    procid = procid or '-'
    msgid = msgid or '-'

    -- HOSTNAME APP-NAME PROCID MSGID
    local header = host_name .. ' ' .. app_name .. ' ' .. procid .. ' ' .. msgid

    return function(msg, lvl, now)
        local slvl = assert(LVL2SYSLOG[lvl])
        return -- HEADER
        -- PRI VERSION TIMESTAMP
        '<' .. slvl + facility .. '>1 ' .. Date2SysLog(now) .. ' ' .. -- HEADER STRUCTURED-DATA MSG
        header .. ' - ' .. msg

    end
end

return M
