# frozen_string_literal: true

require 'tegimen/delegator'

require 'logger'
require 'rainbow'

module Tegimen
  module Logging
    class << self
      def indent_level
        return 0 if Tegimen.parallelize?
        @indent_level ||= 0
      end

      def indent_logs
        @indent_level = indent_level + 1
      end

      def dedent_logs
        @indent_level = [indent_level - 1, 0].max
      end

      def logging_level
        @level ||= :INFO
      end

      def logging_level=(level)
        @level = level
      end

      def extended(target)
        target.extend Tegimen::Delegator
        target.include ClassMethods

        target.delegate_to proc { logger }, :debug, :info, :warn, :error, :fatal, :unknown
      end

      def logger
        @logger ||= Logger.new(STDOUT).tap do |logger|
          logger.level = Logger.const_get(logging_level)
          logger.progname = 'Tegimen'
          logger.formatter = LogFormatter
        end
      end
    end

    module ClassMethods
      def debug(message)
        message = message.indent(Tegimen::Logging.indent_level + 1) if message.respond_to?(:indent)
        self.class.__send__ :debug, message
      end

      def important(message)
        # Always show these info statements, always.
        orig_level = self.class.logger.level
        self.class.logger.level = Logger.const_get(:INFO)

        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :info, message

        # restore original logging level
        self.class.logger.level = orig_level
      end

      def info(message)
        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :info, message
      end

      def warn(message)
        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :warn, message
      end

      def error(message)
        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :error, message
      end

      def fatal(message)
        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :fatal, message
      end

      def unknown(message)
        message = message.indent(Tegimen::Logging.indent_level) if Tegimen::Logging.indent_level.positive? && message.respond_to?(:indent)
        self.class.__send__ :unknown, message
      end

      def indent_logs
        Tegimen::Logging.indent_logs
      end

      def dedent_logs
        Tegimen::Logging.dedent_logs
      end
    end

    def logger
      Tegimen::Logging.logger
    end
  end
end

class LogFormatter
  class << self
    attr_accessor :log_format
  end

  self.log_format = "[%s] %s (%s) -> %s\n"

  class << self
    def call(severity, time, program_name, message)
      format(log_format, format_time(time), color_severity(severity.rjust(8)), program_name, message_output(message))
    end

    private

    def format_time(time)
      time.utc.strftime('%Y-%m-%d %H:%M:%S')
    end

    def color_severity(severity)
      case severity
        when /DEBUG/
          Rainbow(severity).cyan.italic
        when /INFO/
          Rainbow(severity).cyan
        when /WARN/
          Rainbow(severity).yellow
        when /ERROR/, /FATAL/, /UNKNOWN/
          Rainbow(severity).red
        else
          severity
      end
    end

    def message_output(message)
      case message
        when ::String
          message
        when ::Exception
          parts = ['']
          parts << Rainbow('Message: ').magenta
          parts << "#{message.message} (#{message.class})".indent(1, 4)
          unless message.backtrace.nil?
            parts << Rainbow('Backtrace:').magenta
            parts += message.backtrace.collect { |line| line.indent(1, 4) }
          end
          parts.join("\n")
        else
          message.inspect
      end
    end
  end
end
