#
# ServerEngine
#
# Copyright (C) 2012-2013 Sadayuki Furuhashi
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#
require 'logger'

module ServerEngine

  class ::Logger::LogDevice
    def reopen!
      if filename = @filename
        @dev.reopen(filename, 'a')
        @dev.sync = true
      end
    end
  end

  class DaemonLogger < Logger
    def initialize(logdev, config={})
      @rotate_age = config[:log_rotate_age] || 5
      @rotate_size = config[:log_rotate_size] || 1048576
      @file_dev = nil

      super(nil)

      self.level = config[:log_level] || 'debug'
      self.logdev = logdev
    end

    def logdev=(logdev)
      # overwrites Logger's @logdev variable
      if logdev.respond_to?(:write) and logdev.respond_to?(:close)
        # IO
        @logdev = logdev
        @logdev.sync = true if @logdev.respond_to?(:sync=)
        if @file_dev
          old_file_dev = @file_dev
          @file_dev = nil
          old_file_dev.close
        end
      elsif !@file_dev || @file_dev.filename != logdev
        # update path string
        old_file_dev = @file_dev
        @file_dev = LogDevice.new(logdev, shift_age: @rotate_age, shift_size: @rotate_size)
        # Enable to detect rotation done by external tools.
        # Otherwise it continues writing logs to old file unexpectedly.
        @file_dev.extend(RotationAware)
        old_file_dev.close if old_file_dev
        @logdev = @file_dev
      end
      logdev
    end

    # override add method
    def add(severity, message = nil, progname = nil, &block)
      if severity < @level
        return true
      end
      if message.nil?
        if block_given?
          message = yield
        else
          message = progname
          progname = nil
        end
      end
      progname ||= @progname
      self << format_message(SEVERITY_FORMATS_[severity+1], Time.now, progname, message)
      true
    end

    module Severity
      include Logger::Severity
      TRACE = -1
    end
    include Severity

    SEVERITY_FORMATS_ = %w(TRACE DEBUG INFO WARN ERROR FATAL ANY)

    def level=(expr)
      case expr.to_s
      when 'fatal', FATAL.to_s
        e = FATAL
      when 'error', ERROR.to_s
        e = ERROR
      when 'warn', WARN.to_s
        e = WARN
      when 'info', INFO.to_s
        e = INFO
      when 'debug', DEBUG.to_s
        e = DEBUG
      when 'trace', TRACE.to_s
        e = TRACE
      else
        raise ArgumentError, "invalid log level: #{expr}"
      end

      super(e)
    end

    def trace?; @level <= TRACE; end

    def reopen!
      @file_dev.reopen! if @file_dev
      nil
    end

    def reopen
      begin
        reopen!
        return true
      rescue
        # TODO log?
        return false
      end
    end

    def close
      @file_dev.close if @file_dev
      nil
    end

    module RotationAware
      def self.extended(obj)
        obj.update_ino
      end

      def update_ino
        (@ino_mutex ||= Mutex.new).synchronize do
          @ino = File.stat(filename).ino rescue nil
          @last_ino_time = Time.now
        end
      end

      def reopen(log = nil)
        super(log)
        update_ino
      end

      def reopen!
        super
        update_ino
      end

      def write(message)
        reopen_needed = false
        @ino_mutex.synchronize do
          if (Time.now - @last_ino_time).abs > 1
            ino = File.stat(filename).ino rescue nil
            reopen_needed = true if ino && ino != @ino
          end
        end
        reopen! if reopen_needed
        super(message)
      end
    end
  end

end
