require "progress_bar"
module InspecPlugins::StreamingReporterProgressBar
  # This class will provide the actual Streaming Reporter implementation.
  # Its superclass is provided by another call to Inspec.plugin,
  # this time with two args.  The first arg specifies we are requesting
  # version 2 of the Plugins API.  The second says we are making a
  # Streaming Reporter plugin component, so please make available any DSL needed
  # for that.

  class StreamingReporter < Inspec.plugin(2, :streaming_reporter)
    # Registering these methods with RSpec::Core::Formatters class is mandatory
    RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending

    case RUBY_PLATFORM
    when /windows|mswin|msys|mingw|cygwin/
      # Most currently available Windows terminals have poor support
      # for ANSI extended colors
      COLORS = {
        "failed" => "\033[0;1;31m",
        "passed" => "\033[0;1;32m",
        "skipped" => "\033[0;37m",
        "reset" => "\033[0m",
        "error" => "\033[34m",
        "not_applicable" => "\033[36m",
        "not_reviewed" => "\033[33m",
      }.freeze

      # Most currently available Windows terminals have poor support
      # for UTF-8 characters so use these boring indicators
      INDICATORS = {
        "failed" => "[FAIL]",
        "skipped" => "[SKIP]",
        "passed" => "[PASS]",
        "error" => "  [ERROR]  ",
        "not_applicable" => "  [N/A]    ",
        "not_reviewed" => "  [N/R]    ",
      }.freeze
    else
      # Extended colors for everyone else
      COLORS = {
        "failed" => "\033[38;5;9m",
        "passed" => "\033[38;5;41m",
        "skipped" => "\033[38;5;247m",
        "reset" => "\033[0m",
        "error" => "\033[0;38;5;21m",
        "not_applicable" => "\033[0;38;5;117m",
        "not_reviewed" => "\033[0;38;5;214m",
      }.freeze

      # Groovy UTF-8 characters for everyone else...
      # ...even though they probably only work on Mac
      INDICATORS = {
        "failed" => "× [FAILED] ",
        "skipped" => "↺ [SKIPPED]",
        "passed" => "✔ [PASSED] ",
        "error" => "× [ERROR]  ",
        "not_applicable" => "  [N/A]    ",
        "not_reviewed" => "  [N/R]    ",
      }.freeze
    end

    def initialize(output)
      @bar = nil
      @status_mapping = {}
      initialize_streaming_reporter
    end

    def example_passed(notification)
      set_example(notification, "passed")
    end

    def example_failed(notification)
      set_example(notification, "failed")
    end

    def example_pending(notification)
      set_example(notification, "skipped")
    end

    private

    def set_example(notification, status)
      control_id = notification.example.metadata[:id]
      title = notification.example.metadata[:title]
      full_description = notification.example.metadata[:full_description]

      # No-op exception occurs in case of not_applicable_if
      if (full_description.include? "No-op") && notification.example.exception
        full_description += notification.example.exception.message
      end

      set_status_mapping(control_id, status)
      collect_notifications(notification, control_id, status)
      control_ended = control_ended?(control_id)
      if control_ended
        control_outcome = add_enhanced_outcomes(control_id) if enhanced_outcomes
        show_progress(control_id, title, full_description, control_outcome)
      end
    end

    def show_progress(control_id, title, full_description, control_outcome)
      @bar ||= ProgressBar.new(controls_count, :bar, :counter, :percentage)
      sleep 0.1
      @bar.increment!
      @bar.puts format_it(control_id, title, full_description, control_outcome)
    rescue StandardError => e
      raise "Exception in Progress Bar streaming reporter: #{e}"
    end

    def format_it(control_id, title, full_description, control_outcome)
      if control_outcome
        control_status = control_outcome
      else
        control_status = if @status_mapping[control_id].include? "failed"
                           "failed"
                         elsif @status_mapping[control_id].include? "passed"
                           "passed"
                         else
                           @status_mapping[control_id].include? "skipped"
                           "skipped"
                         end
      end
      indicator = INDICATORS[control_status]
      message_to_format = ""
      message_to_format += "#{indicator}  "
      message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)}  "
      message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)}  " if title
      message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)}  " unless title
      format_with_color(control_status, message_to_format)
    rescue Exception => e
      raise "Exception in show_progress: #{e}"
    end

    def format_with_color(color_name, text)
      "#{COLORS[color_name]}#{text}#{COLORS["reset"]}"
    rescue StandardError => e
      raise "Exception in format_with_color: #{e}"
    end

    # status mapping with control id to decide the final state of the control
    def set_status_mapping(control_id, status)
      @status_mapping[control_id] = [] if @status_mapping[control_id].nil?
      @status_mapping[control_id].push(status)
    rescue StandardError => e
      raise "Exception in format_with_color: #{e}"
    end

  end
end
