#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'spec/expectations'
require 'spec/matchers'
require 'spec/example/pending'

module Spec
  module Story
=begin
  A World represents the actual instance a scenario will run in.
  
  The runner ensures any instance variables and methods defined anywhere
  in a story block are available to all the scenarios. This includes
  variables that are created or referenced inside Given, When and Then
  blocks.
=end
    module World
      include ::Spec::Example::Pending
      include ::Spec::Matchers

      def self.create(cls = Object, *args)
        cls.new(*args).extend(World)
      end
      
      def self.listeners
        @listeners ||= []
      end
      
      def self.add_listener(listener)
        listeners() << listener
      end
      
      def self.step_mother
        @step_mother ||= StepMother.new
      end
              
      def self.use(steps)
        step_mother.use(steps)
      end
      
      def self.step_names
        @step_names ||= []
      end

      def self.run_given_scenario_with_suspended_listeners(world, type, name, scenario)
        current_listeners = Array.new(listeners)
        begin
          listeners.each { |l| l.found_scenario(type, name) }
          @listeners.clear
          scenario.perform(world, name) unless dry_run
        ensure
          @listeners.replace(current_listeners)
        end
      end
      
      def self.store_and_call(world, type, name, *args, &block)
        if block_given?
          step_mother.store(type, Step.new(name, &block))
        end
        step = step_mother.find(type, name)

        step_name = step.name
        step_names << step_name
        
        # It's important to have access to the parsed args here, so
        # we can give them to the listeners. The HTML reporter needs
        # the args so it can style them. See the generated output in
        # story_server/prototype/rspec_stories.html (generated by rake stories)
        args = step.parse_args(name) if args.empty?
        begin
          listeners.each { |l| l.step_upcoming(type, step_name, *args) }
          step.perform(world, *args) unless dry_run
          listeners.each { |l| l.step_succeeded(type, step_name, *args) }
        rescue Exception => e
          case e
          when Spec::Example::ExamplePendingError
            @listeners.each { |l| l.step_pending(type, step_name, *args) }
          else
            @listeners.each { |l| l.step_failed(type, step_name, *args) }
          end
          errors << e
        end
      end
      
      def self.errors
        @errors ||= []
      end
      
      def self.dry_run
        ::Spec::Story::Runner.dry_run
      end
      
      def start_collecting_errors
        errors.clear
      end
      
      def errors
        World.errors
      end
      
      def GivenScenario(name)
        World.run_given_scenario_with_suspended_listeners(self, :'given scenario', name, GivenScenario.new(name))
        @__previous_step = :given
      end
      
      def Given(name, *args, &block)
        World.store_and_call self, :given, name, *args, &block
        @__previous_step = :given
      end
      
      def When(name, *args, &block)
        World.store_and_call self, :when, name, *args, &block
        @__previous_step = :when
      end
      
      def Then(name, *args, &block)
        World.store_and_call self, :then, name, *args, &block
        @__previous_step = :then
      end
      
      def And(name, *args, &block)
        World.store_and_call self, @__previous_step, name, *args, &block
      end
    end
  end
end
