-- gitano.log
--
-- Statement logging depending on log level etc.
--
-- Copyright 2012-2017 Daniel Silverstone <dsilvers@digital-scurf.org>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the author nor the names of their contributors
--    may be used to endorse or promote products derived from this software
--    without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.
--

local luxio = require "luxio"
local sio = require "luxio.simple"
local os = require "os"

local concat = table.concat

local prefix = "[gitano] "
local transactionid = nil

local stream = sio.stderr
local is_buffered = false

local ERRS = 0
local WARN = 1
local CHAT = 2
local INFO = 3
local DEBUG = 4
local DEEPDEBUG = 5

local level = ERRS

local LogBuf = {}
LogBuf.__index = LogBuf

function LogBuf:new()
   return setmetatable({strings = {}}, self)
end

function LogBuf:write(s)
   table.insert(self.strings, s)
end

function LogBuf:get()
   return table.concat(self.strings)
end

local function is_buffered_output()
   return is_buffered
end

local function buffer_output()
   if not is_buffered_output() then
      stream = LogBuf:new()
      is_buffered = true
   end
end

local function get_buffered_output()
   if is_buffered_output() then
      return stream:get()
   else
      return nil
   end
end

local function syslog_write(priority, ...)
   local strs = {...}

   for i = 1, #strs do
      strs[i] = tostring(strs[i]) or "?"
   end

   luxio.syslog(priority, transactionid .. ": " .. concat(strs, " ") .. "\n")
end

local function syslog_open()
   local ident = "gitano"
   transactionid = luxio.getenv("GITANO_TRANSACTION_ID")

   if not transactionid then
      transactionid = tostring(luxio.getpid()) .. "." .. os.date("%H%M%S")
   end

   luxio.openlog(ident, 0, luxio.LOG_DAEMON)

   return transactionid
end

local function syslog_close()
   luxio.closelog()
end

local function syslog_error(...)
   syslog_write(luxio.LOG_ERR, ...)
end

local function syslog_warning(...)
   syslog_write(luxio.LOG_WARNING, ...)
end

local function syslog_notice(...)
   syslog_write(luxio.LOG_NOTICE, ...)
end

local function syslog_info(...)
   syslog_write(luxio.LOG_INFO, ...)
end

local function syslog_debug(...)
   syslog_write(luxio.LOG_DEBUG, ...)
end

local function set_prefix(new_prefix)
   if not new_prefix then
      prefix = ""
   else
      if new_prefix == "true" then
         new_prefix = "Gitano"
      end
      prefix = "[" .. tostring(new_prefix) .. "] "
   end
end

local function AT(LVL, ...)
   if level >= LVL then
      local strs = {...}
      for i = 1, #strs do
         strs[i] = tostring(strs[i]) or "?"
      end
      stream:write(prefix .. concat(strs, " ") .. "\n")
   end
end

local function state(...)
   return AT(-1, ...)
end

local function stdout(...)
   local savedstream, savedprefix = stream, prefix

   prefix = ""
   if not is_buffered_output() then
      stream = sio.stdout
   end

   state(...)
   stream, prefix = savedstream, savedprefix
end

local function fatal(...)
   syslog_write(luxio.LOG_CRIT, ...)
   AT(ERRS, "FATAL:", ...)

   if is_buffered_output() then
      sio.stderr:write(get_buffered_output())
   else
      stream:close()
   end

   luxio._exit(1)
end

local function critical(...)
   syslog_write(luxio.LOG_CRIT, ...)
   return AT(ERRS, "CRIT:", ...)
end

local function error(...)
   return AT(ERRS, "ERROR:", ...)
end

local function warning(...)
   return AT(WARN, "WARNING:", ...)
end
local warn = warning

local function chat(...)
   return AT(CHAT, ...)
end

local function info(...)
   return AT(INFO, "INFO:", ...)
end

local function debug(...)
   return AT(DEBUG, "DEBUG:", ...)
end

local function deepdebug(...)
   return AT(DEEPDEBUG, "DEEPDEBUG:", ...)
end

local function set_level(l)
   if type(l) ~= "number" or l < ERRS or l > DEEPDEBUG then
      level = WARN
      warn("Attempted to set level to", tostring(l), "- defaulted to warnings")
   end
   level = l
end

local function bump_level(l)
   if type(l) ~= "number" or l < ERRS or l > DEEPDEBUG then
      warn("Attempted to bump level to", tostring(l), "- left alone")
   end
   if level < l then level = l end
end

local function cap_level(l)
   if type(l) ~= "number" or l < ERRS or l > DEEPDEBUG then
      warn("Attempted to cap level to", tostring(l), "- left alone")
   end
   if level > l then level = l end
end

local function get_level()
   return level
end

-- Check for default log level based on environment
do
   local loglevel = (
      luxio.getenv("LC_GITANO_LOG_LEVEL") or luxio.getenv("GITANO_LOG_LEVEL"))
   if loglevel then
      if tonumber(loglevel) then
         set_level(tonumber(loglevel))
      else
         loglevel = loglevel:lower()
         if (loglevel == "warn" or loglevel == "warning" or
             loglevel == "warnings") then
            level = WARN
         elseif loglevel == "chat" then
            level = CHAT
         elseif loglevel == "info" then
            level = INFO
         elseif loglevel == "debug" then
            level = DEBUG
         elseif (loglevel == "deepdebug" or loglevel == "ddebug" or
                 loglevel == "all") then
            level = DEEPDEBUG
         end
      end
      if level == nil then
         level = WARN
      end
   end

   local do_debug = luxio.getenv("LC_GITANO_DEBUG") or
      luxio.getenv("GITANO_DEBUG")
   if do_debug and do_debug ~= "" then
      level = DEBUG
   end
end

return {
   level = {
      ALL = ERRS,
      ERRS = ERRS,
      WARN = WARN,
      CHAT = CHAT,
      INFO = INFO,
      DEBUG = DEBUG,
      DDEBUG = DEEPDEBUG,
      DEEPDEBUG = DEEPDEBUG,
   },
   set_level = set_level,
   get_level = get_level,
   bump_level = bump_level,
   cap_level = cap_level,
   state = state,
   crit = critical,
   critical = critical,
   err = error,
   error = error,
   warn = warning,
   warning = warning,
   chat = chat,
   info = info,
   debug = debug,
   ddebug = deepdebug,
   deepdebug = deepdebug,
   fatal = fatal,
   stdout = stdout,
   set_prefix = set_prefix,
   syslog = {
      open = syslog_open,
      err = syslog_error,
      error = syslog_error,
      warn = syslog_warning,
      warning = syslog_warning,
      notice = syslog_notice,
      info = syslog_info,
      debug = syslog_debug,
      close = syslog_close,
   },
   buffer_output = buffer_output,
   is_buffered_output = is_buffered_output,
   get_buffered_output = get_buffered_output
}
