require 'logger'

# A simple wrapper arround the Ruby logger. Mainly for 
# compatibility purposes.
#
#--
# TODO: implement as mixin that can be added to Log4R.
#++

class Logger
  alias_method :devel, :debug
  alias_method :fine, :debug
  
  # Prints a trace message to DEBUGLOG (at debug level).  
  # Useful for emitting the value of variables, etc.  Use 
  # like this:
  #
  #   x = y = 5
  #   trace 'x'        # -> 'x = 5'
  #   trace 'x ** y'   # -> 'x ** y = 3125'
  #
  # If you have a more complicated value, like an array of 
  # hashes, then you'll probably want to use an alternative 
  # output format.  For instance:
  #
  #   trace 'value', :yaml
  #
  # Valid output format values (the _style_ parameter) are:
  #
  #   :p :inspect
  #   :pp                     (pretty-print, using 'pp' library)
  #   :s :to_s
  #   :y :yaml :to_yaml       (using the 'yaml' library')
  #
  # The default is <tt>:p</tt>.
  #
  # CREDITS:
  # 
  # This code comes straight from the dev-utils Gem.
  # Author: Gavin Sinclair <gsinclair@soyabean.com.au>
  
  def trace(expr, style=:p)
    unless expr.respond_to? :to_str
      warn "trace: Can't evaluate the given value: #{caller.first}"
    else
      require 'facet/binding/self/of_caller'

      Binding.of_caller do |b|
        value = b.eval(expr.to_str)
        formatter = TRACE_STYLES[style] || :inspect
        case formatter
        when :pp then require 'pp'
        when :y, :yaml, :to_yaml then require 'yaml'
        end
        value_s = value.send(formatter)
        message = "#{expr} = #{value_s}"
        lines = message.split(/\n/)
        indent = "   "
        debug(lines.shift)                      
        lines.each do |line|
          debug(indent + line)
        end
      end
    end
  end

  TRACE_STYLES = {}  # :nodoc:
  TRACE_STYLES.update( 
    :pp => :pp_s, :s => :to_s, :p => :inspect, 
    :y => :to_yaml, :yaml => :to_yaml, 
    :inspect => :inspect, :to_yaml => :to_yaml 
  )

  # Dictate the way in which this logger should format the 
  # messages it displays. This method requires a block. The 
  # block should return formatted strings given severity, 
  # timestamp, msg, progname.
  #
  # Useless example:
  #
  # logger = Logger.new
  # logger.format do |severity, timestamp, msg, progname|
  #   "#{progname}@#{timestamp} - #{severity}::#{msg}"
  # end

  def setup_format(&format_proc)
#    raise 'Formating block needed' unless format_proc
#    @format_proc = format_proc
  end

private
  
  # hackish use of *args, give me some love.
  
  alias_method :old_format_message, :format_message
  def format_message(*args)
    @format_proc ? @format_proc.call(*args) : old_format_message(*args)
  end
end

# Global logger interface. This provides an alternative 
# Singleton interface to the Logger.

class Logger

  SIMPLE_FORMAT = "%5s: %s\n"
  @@global_logger = Logger.new(STDERR)
  @@global_logger.setup_format do |severity, timestamp, progname, msg|  
    SIMPLE_FORMAT % [severity, msg]
  end
  
  # Set the global Logger.
  
  def self.set(logger)
    if logger.is_a?(String)
      @@global_logger = Logger.new(logger)
      @@global_logger.setup_format do |severity, timestamp, progname, msg|  
        SIMPLE_FORMAT % [severity, msg]
      end
    elsif logger.is_a?(Logger)
      @@global_logger = logger
      @@global_logger.setup_format do |severity, timestamp, progname, msg|  
        SIMPLE_FORMAT % [severity, msg]
      end
    else
      raise ArgumentError.new
    end
  end

  def self.get
    @@global_logger
  end
  
  def self.warn(str)
    @@global_logger.warn(str)
  end

  def self.info(str)
    @@global_logger.info(str)
  end

  def self.debug(str)
    @@global_logger.debug(str)
  end
  
  def self.error(str)
    @@global_logger.error(str)
  end

  #--
  # Saddly have to duplicate the code to make
  # Binding.of_caller work.
  #++
  def self.trace(expr, style=:p)
    unless expr.respond_to? :to_str
      warn "trace: Can't evaluate the given value: #{caller.first}"
    else
      require 'facet/binding/self/of_caller'

      Binding.of_caller do |b|
        value = eval(expr.to_str, b)
        formatter = TRACE_STYLES[style] || :inspect
        case formatter
        when :pp then require 'pp'
        when :y, :yaml, :to_yaml then require 'yaml'
        end
        value_s = value.send(formatter)
        message = "#{expr} = #{value_s}"
        lines = message.split(/\n/)
        indent = "   "
        debug(lines.shift)                      
        lines.each do |line|
          debug(indent + line)
        end
      end
    end
  end
end

module Glue

# Add logging capabilities to the including class.
# When using the log/logger variables always check 
# for nil:
#
# log.info "your #{expensive_calculation} comes here" if @log
#
# This way the expensive calculation is avoided if the 
# logger is dissabled (@log == nil).

module Logging 
  attr_accessor :logger
  alias_method :log, :logger
end

end

# * George Moschovitis  <gm@navel.gr>