require 'fileutils'
require 'gherkin/rubify'
require 'gherkin/i18n'
require 'cucumber/configuration'
require 'cucumber/language_support/language_methods'
require 'cucumber/formatter/duration'
require 'cucumber/runtime/user_interface'
require 'cucumber/runtime/features_loader'
require 'cucumber/runtime/results'
require 'cucumber/runtime/support_code'

module Cucumber
  # This is the meaty part of Cucumber that ties everything together.
  class Runtime
    attr_reader :results
    
    include Formatter::Duration
    include Runtime::UserInterface

    def initialize(configuration = Configuration.default)
      require 'cucumber/core_ext/disable_mini_and_test_unit_autorun'
      @current_scenario = nil
      @configuration = Configuration.parse(configuration)
      @support_code = SupportCode.new(self, @configuration)
      @results = Results.new(@configuration)
    end
    
    # Allows you to take an existing runtime and change it's configuration
    def configure(new_configuration)
      @configuration = Configuration.parse(new_configuration)
      @support_code.configure(@configuration)
      @results.configure(@configuration)
    end
    
    def load_programming_language(language)
      @support_code.load_programming_language(language)
    end
    
    def run!
      load_step_definitions
      fire_after_configuration_hook

      tree_walker = @configuration.build_tree_walker(self)
      self.visitor = tree_walker # Ugly circular dependency, but needed to support World#puts
      
      tree_walker.visit_features(features)
    end
    
    def features_paths
      @configuration.paths
    end

    def step_visited(step) #:nodoc:
      @results.step_visited(step)
    end

    def scenarios(status = nil)
      @results.scenarios(status)
    end

    def steps(status = nil)
      @results.steps(status)
    end

    def step_match(step_name, name_to_report=nil) #:nodoc:
      @support_code.step_match(step_name, name_to_report)
    end

    def unmatched_step_definitions
      @support_code.unmatched_step_definitions
    end

    def snippet_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
      @support_code.snippet_text(Gherkin::I18n.code_keyword_for(step_keyword), step_name, multiline_arg_class)
    end

    def with_hooks(scenario, skip_hooks=false)
      around(scenario, skip_hooks) do
        before_and_after(scenario, skip_hooks) do
          yield scenario
        end
      end
    end

    def around(scenario, skip_hooks=false, &block) #:nodoc:
      if skip_hooks
        yield
        return
      end

      @support_code.around(scenario, block)
    end

    def before_and_after(scenario, skip_hooks=false) #:nodoc:
      before(scenario) unless skip_hooks
      yield scenario
      after(scenario) unless skip_hooks
      @results.scenario_visited(scenario)
    end

    def before(scenario) #:nodoc:
      return if @configuration.dry_run? || @current_scenario
      @current_scenario = scenario
      @support_code.fire_hook(:before, scenario)
    end

    def after(scenario) #:nodoc:
      @current_scenario = nil
      return if @configuration.dry_run?
      @support_code.fire_hook(:after, scenario)
    end

    def after_step #:nodoc:
      return if @configuration.dry_run?
      @support_code.fire_hook(:execute_after_step, @current_scenario)
    end

    def unknown_programming_language?
      @support_code.unknown_programming_language?
    end

    def write_stepdefs_json
      if(@configuration.dotcucumber)
        stepdefs = []
        @support_code.step_definitions.sort{|a,b| a.to_hash['source'] <=> a.to_hash['source']}.each do |stepdef|
          stepdef_hash = stepdef.to_hash
          steps = []
          features.each do |feature|
            feature.feature_elements.each do |feature_element|
              feature_element.raw_steps.each do |step|
                args = stepdef.arguments_from(step.name)
                if(args)
                  steps << {
                    'name' => step.name,
                    'args' => args.map do |arg|
                      {
                        'offset' => arg.offset,
                        'val' => arg.val
                      }
                    end
                  }
                end
              end
            end
          end
          stepdef_hash['file_colon_line'] = stepdef.file_colon_line
          stepdef_hash['steps'] = steps.uniq.sort {|a,b| a['name'] <=> b['name']}
          stepdefs << stepdef_hash
        end
        if !File.directory?(@configuration.dotcucumber)
          FileUtils.mkdir_p(@configuration.dotcucumber)
        end
        File.open(File.join(@configuration.dotcucumber, 'stepdefs.json'), 'w') do |io|
          io.write(JSON.pretty_generate(stepdefs))
        end
      end
    end

  private

    def fire_after_configuration_hook #:nodoc
      @support_code.fire_hook(:after_configuration, @configuration)
    end

    def features
      @loader ||= Runtime::FeaturesLoader.new(
        @configuration.feature_files, 
        @configuration.filters, 
        @configuration.tag_expression)
      @loader.features
    end

    def load_step_definitions
      files = @configuration.support_to_load + @configuration.step_defs_to_load
      @support_code.load_files!(files)
    end

    def log
      Cucumber.logger
    end
  end

end