require 'ansi/code'
require 'powerbar'

module MiniTest
  module Reporters
    # Fuubar-like reporter with a progress bar.
    #
    # Based upon Jeff Kreefmeijer's Fuubar (MIT License) and paydro's
    # monkey-patch.
    #
    # @see https://github.com/jeffkreeftmeijer/fuubar Fuubar
    # @see https://gist.github.com/356945 paydro's monkey-patch
    class ProgressReporter
      include Reporter
      include ANSI::Code

      INFO_PADDING = 2

      def initialize(options = {})
        @detailed_skip = options.fetch(:detailed_skip, true)

        @progress = PowerBar.new(:msg => "0/#{runner.test_count}")
        @progress.settings.tty.finite.output = lambda { |s| print(s) }
        @progress.settings.tty.finite.template.barchar = "="
        @progress.settings.tty.finite.template.padchar = " "
        @progress.settings.tty.finite.template.pre = "\e[1000D\e[?25l#{GREEN}"
        @progress.settings.tty.finite.template.post = CLEAR
      end

      def before_suites(suites, type)
        puts 'Started'
        puts

        @finished_count = 0
        show
      end

      def increment
        @finished_count += 1
        show
      end

      def show
        return if runner.test_count == 0

        @progress.show({
          :msg => "#{@finished_count}/#{runner.test_count}",
          :done => @finished_count,
          :total => runner.test_count,
        }, true)
      end

      def after_test(suite, test)
        increment
      end

      def skip(suite, test, test_runner)
        if @detailed_skip
          wipe
          print(yellow { 'SKIP' })
          print_test_with_time(suite, test)
          puts
          puts
        end

        self.color = YELLOW unless color == RED
      end

      def failure(suite, test, test_runner)
        wipe
        print(red { 'FAIL' })
        print_test_with_time(suite, test)
        puts
        print_info(test_runner.exception, false)
        puts

        self.color = RED
      end

      def error(suite, test, test_runner)
        wipe
        print(red { 'ERROR' })
        print_test_with_time(suite, test)
        puts
        print_info(test_runner.exception)
        puts

        self.color = RED
      end

      def after_suites(suites, type)
        @progress.close

        total_time = Time.now - runner.suites_start_time

        wipe
        puts
        puts('Finished in %.5fs' % total_time)
        print('%d tests, %d assertions, ' % [runner.test_count, runner.assertion_count])
        print(red { '%d failures, %d errors, ' } % [runner.failures, runner.errors])
        print(yellow { '%d skips' } % runner.skips)
        puts
      end

      private

      def wipe
        @progress.wipe
      end

      def print_test_with_time(suite, test)
        total_time = Time.now - (runner.test_start_time || Time.now)
        print(" %s#%s (%.2fs)%s" % [suite, test, total_time, clr])
      end

      def print_info(e, name = true)
        print pad("#{e.exception.class.to_s}: ") if name
        e.message.each_line { |line| puts pad(line) }

        trace = filter_backtrace(e.backtrace)
        trace.each { |line| puts pad(line) }
      end

      def pad(str)
        ' ' * INFO_PADDING + str
      end

      def color
        @color ||= GREEN
      end

      def color=(color)
        @color = color
        @progress.scope.template.pre = "\e[1000D\e[?25l#{@color}"
      end
    end
  end
end
