# Now load ruby-prof and away we go
require 'fileutils'
require 'ruby-prof'
require 'benchmark'

module RubyProf
  module Test
    PROFILE_OPTIONS = {
      :measure_modes => [RubyProf::PROCESS_TIME],
      :count => 10,
      :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter],
      :min_percent => 0.05,
      :output_dir => Dir.pwd }

    def output_dir
      PROFILE_OPTIONS[:output_dir]
    end

    def run(result)
      return if @method_name.to_s == "default_test"

      yield(self.class::STARTED, name)
      @_result = result
      run_warmup
      PROFILE_OPTIONS[:measure_modes].each do |measure_mode|
        data = run_profile(measure_mode)
        report_profile(data, measure_mode)
        result.add_run
      end
      yield(self.class::FINISHED, name)
    end

    def run_test
      begin
        setup
        yield
      rescue ::Test::Unit::AssertionFailedError => e
        add_failure(e.message, e.backtrace)
      rescue StandardError, ScriptError
        add_error($!)
      ensure
        begin
          teardown
        rescue ::Test::Unit::AssertionFailedError => e
          add_failure(e.message, e.backtrace)
        rescue StandardError, ScriptError
          add_error($!)
        end
      end
    end

    def run_warmup
      print "\n#{self.class.name}##{method_name}"

      run_test do
        bench = Benchmark.realtime do
          __send__(@method_name)
        end
        puts " (%.2fs warmup)" % bench
      end
    end

    def run_profile(measure_mode)
      RubyProf.measure_mode = measure_mode

      print '  '
      PROFILE_OPTIONS[:count].times do |i|
        run_test do
          begin
            print '.'
            $stdout.flush
            GC.disable

            RubyProf.resume do
              __send__(@method_name)
            end
          ensure
            GC.enable
          end
        end
      end

      data = RubyProf.stop
      bench = data.threads.values.inject(0) do |total, method_infos|
        top = method_infos.max
        total += top.total_time
        total
      end

      puts "\n  #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n"

      data
    end

    def format_profile_total(total, measure_mode)
      case measure_mode
        when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME
          "%.2f seconds" % total
        when RubyProf::MEMORY
          "%.2f kilobytes" % total
        when RubyProf::ALLOCATIONS
          "%d allocations" % total
        else
          "%.2f #{measure_mode}"
      end
    end

    def report_profile(data, measure_mode)
      PROFILE_OPTIONS[:printers].each do |printer_klass|
        printer = printer_klass.new(data)

        # Makes sure the output directory exits
        FileUtils.mkdir_p(output_dir)

        # Open the file
        file_name = report_filename(printer, measure_mode)

        File.open(file_name, 'wb') do |file|
          printer.print(file, PROFILE_OPTIONS)
        end
      end
    end

    # The report filename is test_name + measure_mode + report_type
    def report_filename(printer, measure_mode)
      suffix =
        case printer
          when RubyProf::FlatPrinter; 'flat.txt'
          when RubyProf::GraphPrinter; 'graph.txt'
          when RubyProf::GraphHtmlPrinter; 'graph.html'
          when RubyProf::CallTreePrinter; 'tree.txt'
          else printer.to_s.downcase
        end

      "#{output_dir}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}"
    end

    def measure_mode_name(measure_mode)
      case measure_mode
        when RubyProf::PROCESS_TIME; 'process_time'
        when RubyProf::WALL_TIME; 'wall_time'
        when RubyProf::MEMORY; 'memory'
        when RubyProf::ALLOCATIONS; 'allocations'
        else "measure#{measure_mode}"
      end
    end
  end
end
