# encoding: UTF-8
#-- vim:sw=2:et
#++
#
# :title: rbot logger

require 'logger'
require 'thread'
require 'singleton'

module Irc
class Bot

  class LoggerManager
    include Singleton

    def enable_console_logger
      @console_logger = Logger.new(STDERR)
      @console_logger.datetime_format = @dateformat
      @console_logger.level = Logger::Severity::DEBUG
    end

    def disable_console_logger
      @console_logger = nil
    end

    def initialize
      @dateformat = "%Y/%m/%d %H:%M:%S"

      enable_console_logger

      @file_logger = nil

      @queue = Queue.new
      start_thread
    end

    def set_logfile(filename, keep, max_size)
      # close previous file logger if present
      @file_logger.close if @file_logger

      @file_logger = Logger.new(filename, keep, max_size*1024*1024)
      @file_logger.datetime_format = @dateformat
      @file_logger.level = @console_logger.level

      # make sure the thread is running, which might be false after a fork
      # (conveniently, we call set_logfile right after the fork)
      start_thread
    end

    def set_level(level)
      @console_logger.level = level if @console_logger
      @file_logger.level = level if @file_logger
    end

    def sync_log(severity, message = nil, progname = nil)
      @console_logger.add(severity, message, progname) if @console_logger
      @file_logger.add(severity, message, progname) if @file_logger
    end

    def async_log(severity, message=nil, who_pos=1)
      unless @thread
        STDERR.puts('logger thread already destroyed, cannot log message!')
      end

      call_stack = caller
      if call_stack.length > who_pos
        who = call_stack[who_pos].sub(%r{(?:.+)/([^/]+):(\d+)(:in .*)?}) { "#{$1}:#{$2}#{$3}" }
      else
        who = "(unknown)"
      end
      # Output each line. To distinguish between separate messages and multi-line
      # messages originating at the same time, we blank #{who} after the first message
      # is output.
      # Also, we output strings as-is but for other objects we use pretty_inspect
      message = message.kind_of?(String) ? message : (message.pretty_inspect rescue '?')
      qmsg = Array.new
      message.each_line { |l|
        qmsg.push [severity, l.chomp, who]
        who = ' ' * who.size
      }
      @queue.push qmsg
    end

    def log_session_start
      # if we forked, the thread may be dead, so let's restart it if necessary
      start_thread
      if @file_logger
        @file_logger << "\n\n=== session started on #{Time.now.strftime(@dateformat)} ===\n\n"
      end
    end

    def log_session_end
      if @file_logger
        @file_logger << "\n\n=== session ended on #{Time.now.strftime(@dateformat)} ===\n\n"
      end
    end

    def halt_logger
      if @thread and @thread.alive?
        @queue << nil
        @thread.join
        @thread = nil
        log_session_end
      end
    end

    def flush
      while @queue.size > 0
        next
      end
    end

    private

    def start_thread
      return if @thread and @thread.alive?
      @thread = Thread.new do
        lines = nil
        while lines = @queue.pop
          lines.each { |line|
            sync_log(*line)
          }
        end
      end
    end

  end

end
end

def debug(message=nil, who_pos=1)
  Irc::Bot::LoggerManager.instance.async_log(Logger::Severity::DEBUG, message, who_pos)
end

def log(message=nil, who_pos=1)
  Irc::Bot::LoggerManager.instance.async_log(Logger::Severity::INFO, message, who_pos)
end

def warning(message=nil, who_pos=1)
  Irc::Bot::LoggerManager.instance.async_log(Logger::Severity::WARN, message, who_pos)
end

def error(message=nil, who_pos=1)
  Irc::Bot::LoggerManager.instance.async_log(Logger::Severity::ERROR, message, who_pos)
end

def fatal(message=nil, who_pos=1)
  Irc::Bot::LoggerManager.instance.async_log(Logger::Severity::FATAL, message, who_pos)
end
