module Spec
  module Runner
    class Specification
      module ClassMethods
        attr_accessor :current
        protected :current=

        callback_events :before_setup, :after_teardown
      end
      extend ClassMethods

      attr_reader :command
      callback_events :before_setup, :after_teardown

      def initialize(name, opts={}, &block)
        @from = caller(0)[3]
        @name = name
        @options = opts
        @command = block
      end

      def run(reporter, setup_block, teardown_block, dry_run, execution_context)
        reporter.spec_started(@name) if reporter
        return reporter.spec_finished(@name) if dry_run

        errors = []
        begin
          set_current
          setup_ok = setup_spec(execution_context, errors, &setup_block)
          spec_ok = execute_spec(execution_context, errors) if setup_ok
          teardown_ok = teardown_spec(execution_context, errors, &teardown_block)
        ensure
          clear_current
        end

        SpecShouldRaiseHandler.new(@from, @options).handle(errors)
        reporter.spec_finished(@name, errors.first, failure_location(setup_ok, spec_ok, teardown_ok)) if reporter
      end

      def matches_matcher?(matcher)
        matcher.matches? @name 
      end

      private
      def setup_spec(execution_context, errors, &setup_block)
        notify_before_setup(errors)
        execution_context.instance_eval(&setup_block) if setup_block
        return errors.empty?
      rescue => e
        errors << e
        return false
      end

      def execute_spec(execution_context, errors)
        execution_context.instance_eval(&command)
        return true
      rescue => e
        errors << e
        return false
      end

      def teardown_spec(execution_context, errors, &teardown_block)
        execution_context.instance_eval(&teardown_block) if teardown_block
        notify_after_teardown(errors)
        return errors.empty?
      rescue => e
        errors << e
        return false
      end

      def notify_before_setup(errors)
        self.class.send(:notify_callbacks, :before_setup, self, &append_errors(errors))
        notify_callbacks(:before_setup, self, &append_errors(errors))
      end
      
      def notify_after_teardown(errors)
        notify_callbacks(:after_teardown, self, &append_errors(errors))
        self.class.send(:notify_callbacks, :after_teardown, self, &append_errors(errors))
      end
      
      def append_errors(errors)
        proc {|error| errors << error}
      end
      
      def set_current
        self.class.send(:current=, self)
      end

      def clear_current
        self.class.send(:current=, nil)
      end

      def failure_location(setup_ok, spec_ok, teardown_ok)
        return 'setup' unless setup_ok
        return @name unless spec_ok
        return 'teardown' unless teardown_ok
      end
    end
  end
end