require 'thread'

module Pork
  Stat = Struct.new(:io, :start, :mutex,
                    :tests, :assertions, :skips, :failures, :errors,
                    :exceptions)

  Stat::Imp = Module.new do
    attr_accessor :stop
    def initialize io=$stdout, st=Time.now, mu=Mutex.new,
                   t=0, a=0, s=0, f=0, e=0, x=[]
      super
    end
    def incr_assertions; mutex.synchronize{ self.assertions += 1 }; end
    def incr_tests     ; mutex.synchronize{ self.tests      += 1 }; end
    def incr_skips     ; mutex.synchronize{ self.skips      += 1 }; end
    def add_failure err
      mutex.synchronize do
        self.failures += 1
        exceptions << err
      end
    end
    def add_error err
      mutex.synchronize do
        self.errors += 1
        exceptions << err
      end
    end
    def case_pass    msg='.'; io.print msg; end
    def case_skip    msg='s'; io.print msg; end
    def case_failed  msg='F'; io.print msg; end
    def case_errored msg='E'; io.print msg; end
    def passed?; exceptions.size == 0                        ; end
    def numbers; [tests, assertions, failures, errors, skips]; end
    def velocity
      time_spent = stop - start
      [time_spent.round(6),
       (tests / time_spent).round(4),
       (assertions / time_spent).round(4)]
    end
    def report
      self.stop = Time.now
      io.puts
      io.puts report_exceptions
      io.printf("\nFinished in %s seconds, %s tests/s, %s assertions/s \n",
                *velocity)
      io.printf("%s tests, %s assertions, %s failures, %s errors, %s skips\n",
                *numbers)
    end
    def merge stat
      self.class.new(io, start, mutex,
        *to_a.drop(3).zip(stat.to_a.drop(3)).map{ |(a, b)| a + b })
    end

    private
    def report_exceptions
      exceptions.reverse_each.map do |(err, msg, source_location)|
        "\n  #{show_command(source_location)}"   \
        "\n  #{show_backtrace(err)}" \
        "\n#{message(msg)}"          \
        "\n#{show_exception(err)}"
      end
    end

    def show_command source_location
      "Replicate this test with:\n#{command(source_location)}"
    end

    def command source_location
      "#{env(source_location)} #{Gem.ruby} -S #{$0} #{ARGV.join(' ')}"
    end

    def show_backtrace err
      backtrace(err).join("\n  ")
    end

    def backtrace err
      if $VERBOSE
        err.backtrace
      else
        strip(err.backtrace.reject{ |l| l =~ %r{/lib/pork(/\w+)*\.rb:\d+} })
      end
    end

    def message msg
      msg
    end

    def show_exception err
      "#{err.class}: #{err.message}"
    end

    def strip bt
      strip_home(strip_cwd(bt))
    end

    def strip_home bt
      bt.map{ |path| path.sub(ENV['HOME'], '~') }
    end

    def strip_cwd bt
      bt.map{ |path| path.sub("#{Dir.pwd}/", '') }
    end

    def env source_location
      "env #{pork(source_location)} #{pork_mode} #{pork_seed}"
    end

    def pork source_location
      file, line = source_location
      "PORK_TEST='#{strip([file]).join}:#{line}'"
    end

    def pork_mode
      "PORK_MODE=#{Pork.execute_mode}"
    end

    def pork_seed
      "PORK_SEED=#{Pork.seed}"
    end
  end

  Stat.__send__(:include, Stat::Imp)
end