# frozen_string_literal: true

require 'logger'
require 'rainbow'

module Indicina

  module Logging
    class << self
      def extended(target)
        target.include ClassMethods
      end

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

      def indent_level
        return 0 if Indicina.respond_to?(:parallelize?) && Indicina.parallelize?
        @indent_level ||= 0
      end

      def indent_message(message, extra: false)
        level = extra ? indent_level + 1 : indent_level
        message = message.indent(level) if level.positive? && message.respond_to?(:indent)
        message
      end

      def indent_logs
        @indent_level = indent_level + 1
      end

      def log_level
        @log_level ||= :INFO
      end

      def log_level=(level)
        valid_levels = [:debug, :info, :warn, :error, :fatal, :unknown]
        raise "Unable to set log_level to `#{level}`. Must be one of #{valid_levels.inspect}" unless valid_levels.include?(level)

        level = Logger.const_get(level.upcase)
        loggers.each { |logger| logger.level = level }

        @log_level = level
      end

      def logger(progname = 'Indicina')
        loggers << logger = Logger.new(STDOUT).tap do |logger|
          logger.level = Logging.log_level
          logger.progname = progname
          logger.formatter = LogFormatter
        end
        logger
      end

      def loggers
        @loggers ||= []
      end
    end

    module ClassMethods

      def logger
        @logger ||= Logging.logger
      end

      [:debug, :info, :warn, :error, :fatal, :unknown].each do |level|
        define_method(level) do |message, &block|
          logger.__send__(level, Logging.indent_message(message, extra: level == :debug), &block)
        end
      end

      [:indent_logs, :dedent_logs].each do |func|
        define_method(func) do |*args, &block|
          Logging.__send__(func, *args, &block)
        end
      end

      def important(message, &block)
        normal_level = logger.level

        logger.level = Logger.const_get(:INFO)
        logger.__send__(:info, Logging.indent_message(message), &block)

        logger.level = normal_level
      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
end