module Spec
  module Runner
    class Reporter
      attr_reader :options, :example_groups
      
      def initialize(options)
        @options = options
        @options.reporter = self
        clear
      end
      
      def add_example_group(example_group)
        formatters.each do |f|
          f.add_example_group(example_group)
        end
        example_groups << example_group
      end
      
      def example_started(example)
        formatters.each{|f| f.example_started(example)}
      end
      
      def example_finished(example, error=nil)
        @examples << example
        
        if error.nil?
          example_passed(example)
        elsif Spec::Example::ExamplePendingError === error
          example_pending(example, error.pending_caller, error.message)
        else
          example_failed(example, error)
        end
      end

      def failure(example, error)
        backtrace_tweaker.tweak_backtrace(error)
        failure = Failure.new(example, error)
        @failures << failure
        formatters.each do |f|
          f.example_failed(example, @failures.length, failure)
        end
      end
      alias_method :example_failed, :failure

      def start(number_of_examples)
        clear
        @start_time = Time.new
        formatters.each{|f| f.start(number_of_examples)}
      end
  
      def end
        @end_time = Time.new
      end
  
      # Dumps the summary and returns the total number of failures
      def dump
        formatters.each{|f| f.start_dump}
        dump_pending
        dump_failures
        formatters.each do |f|
          f.dump_summary(duration, @examples.length, @failures.length, @pending_count)
          f.close
        end
        @failures.length
      end

    private

      def formatters
        @options.formatters
      end

      def backtrace_tweaker
        @options.backtrace_tweaker
      end
  
      def clear
        @example_groups = []
        @failures = []
        @pending_count = 0
        @examples = []
        @start_time = nil
        @end_time = nil
      end
  
      def dump_failures
        return if @failures.empty?
        @failures.inject(1) do |index, failure|
          formatters.each{|f| f.dump_failure(index, failure)}
          index + 1
        end
      end

      def dump_pending
        formatters.each{|f| f.dump_pending}
      end

      def duration
        return @end_time - @start_time unless (@end_time.nil? or @start_time.nil?)
        return "0.0"
      end
      
      def example_passed(example)
        formatters.each{|f| f.example_passed(example)}
      end

      EXAMPLE_PENDING_DEPRECATION_WARNING = <<-WARNING
        DEPRECATION NOTICE: RSpec's formatters have changed example_pending
        to accept three arguments instead of just two. Please see the rdoc
        for Spec::Runner::Formatter::BaseFormatter#example_pending
        for more information.
          
        Please update any custom formatters to accept the third argument
        to example_pending. Support for example_pending with two arguments
        and this warning message will be removed after the RSpec 1.1.5 release.
      WARNING
      
      def example_pending(example, pending_caller, message="Not Yet Implemented")
        @pending_count += 1
        formatters.each do |formatter|
          if formatter_uses_deprecated_example_pending_method?(formatter)
            Kernel.warn EXAMPLE_PENDING_DEPRECATION_WARNING
            formatter.example_pending(example, message)
          else
            formatter.example_pending(example, message, pending_caller)
          end
        end
      end
      
      def formatter_uses_deprecated_example_pending_method?(formatter)
        formatter.method(:example_pending).arity == 2
      end
      
      class Failure
        attr_reader :example, :exception
        
        def initialize(example, exception)
          @example = example
          @exception = exception
        end

        def header
          if expectation_not_met?
            "'#{example_name}' FAILED"
          elsif pending_fixed?
            "'#{example_name}' FIXED"
          else
            "#{@exception.class.name} in '#{example_name}'"
          end
        end
        
        def pending_fixed?
          @exception.is_a?(Spec::Example::PendingExampleFixedError)
        end

        def expectation_not_met?
          @exception.is_a?(Spec::Expectations::ExpectationNotMetError)
        end

        protected
        def example_name
          @example.__full_description
        end
      end
    end
  end
end
