require 'minitest'
require 'minitest/reporters/base_reporter'
module Minitest
  module Reporters
    def plugin_xs_and_os_init
      Minitest.reporter.reporters.clear
      Minitest.reporter << TravisReporter.new(options[:io], options)
    
    end
    
    class TravisReporter < Minitest::Reporters::BaseReporter
      
      
      def initialize(*)
        super
        @color_enabled = io.respond_to?(:tty?) && io.tty?
        @color_for_result_code  = {
            '.' => :green,
            'E' => :red,
            'F' => :red,
            '$' => :yellow
        }
        @result_code_to_unicode = {
            '.' => "\u{2714}",
            'F' => "\u{2718}",
            'E' => "\u{203C}",
            '$' => "\u{26A1}"
        }
        @color                  = {
            red:    31,
            green:  32,
            yellow: 33,
            blue:   34
        }
      end
      
      def record(result)
        super
        puts
        print result.class_name
        print ' '
        print self.result_name result.name
        print ' ['
        print_result_code(result.result_code)
        print ']'
      end
      
      def start
        super
        io.print "Run options: #{options[:args]} / "
        io.print 'Running:'
      end
      
      def report
        super
        io.sync = true
        
        failing_results = results.reject(&:skipped?)
        skipped_results = results.select(&:skipped?)
        
        color = :green
        color = :yellow if skipped_results.any?
        color = :red if failing_results.any?
        
        if failing_results.any? || skipped_results.any?
          failing_results.each.with_index(1) { |result, index| display_failing(result, index) }
          skipped_results.each.with_index(failing_results.size + 1) { |result, index| display_skipped(result, index) }
        end
        
        io.print "\n\n"
        io.puts statistics
        io.puts color(summary, color)
        
        if failing_results.any?
          io.puts "\nFailed Tests:\n"
          failing_results.each { |result| display_replay_command(result) }
          io.puts "\n\n"
        end
      end
      
      
      def statistics
        'Finished in %.6fs, %.4f runs/s, %.4f assertions/s.' %
            [total_time, count / total_time, assertions / total_time]
      end
      
      def summary # :nodoc:
        [
            pluralize('run', count),
            pluralize('assertion', assertions),
            pluralize('failure', failures),
            pluralize('error', errors),
            pluralize('skip', skips)
        ].join(', ')
      end
      
      def indent(text)
        text.gsub(/^/, '      ')
      end
      
      def display_failing(result, index)
        backtrace = backtrace(result.failure.backtrace)
        message   = result.failure.message
        message   = message.lines.tap(&:pop).join.chomp if result.error?
        
        str = "\n\n"
        str << color('%4d) %s' % [index, result_name(result.name)])
        str << "\n" << color(indent(message), :red)
        str << "\n" << color(backtrace, :blue)
        io.print str
      end
      
      def display_skipped(result, index)
        location = location(result.failure.location)
        str      = "\n\n"
        str << color('%4d) %s [SKIPPED]' % [index, result_name(result.name)], :yellow)
        str << "\n" << indent(color(location, :yellow))
        io.print str
      end
      
      def display_replay_command(result)
        location, line = find_test_file(result)
        return if location.empty?
        
        command = if defined?(Rails) && Rails.version >= '5.0.0'
                    %[bin/rails test #{location}:#{line}]
                  else
                    %[rake TEST=#{location} TESTOPTS="--name=#{result.name}"]
                  end
        
        str = "\n"
        str << color(command, :red)
        
        io.print str
      end
      
      def find_test_file(result)
        location, line = result.method(result.name).source_location
        location       = location.gsub(%r[^.*?/((?:test|spec)/.*?)$], "\\1")
        
        [location, line]
      end
      
      def backtrace(backtrace)
        backtrace = filter_backtrace(backtrace).map { |line| location(line, true) }
        return if backtrace.empty?
        indent(backtrace.join("\n")).gsub(/^(\s+)/, "\\1# ")
      end
      
      def location(location, include_line_number = false)
        regex    = include_line_number ? /^([^:]+:\d+)/ : /^([^:]+)/
        location = File.expand_path(location[regex, 1])
        
        return location unless location.start_with?(Dir.pwd)
        
        location.gsub(%r[^#{Regexp.escape(Dir.pwd)}/], '')
      end
      
      def filter_backtrace(backtrace)
        Minitest.backtrace_filter.filter(backtrace)
      end
      
      def result_name(name)
        name
            .gsub(/^test(_\d+)?_/, '')
            .gsub(/_/, ' ')
      end
      
      def print_result_code(result_code)
        result = @result_code_to_unicode[result_code]
        colors = {
            "\u{2714}" => :green,
            "\u{2718}" => :red,
            "\u{203C}" => :red,
            "\u{26A1}" => :yellow
        }
        io.print color(result_code, colors[result])
      end
      
      def color(string, color = :default)
        if color_enabled?
          color = @color.fetch(color, 0)
          "\e[#{color}m#{string}\e[0m"
        else
          string
        end
      end
      
      def color_enabled?
        @color_enabled
      end
      
      def pluralize(word, count)
        case count
          when 0
            "no #{word}s"
          when 1
            "1 #{word}"
          else
            "#{count} #{word}s"
        end
      end
    end
  end

end