module Spec
  module Helpers
    def reset!
      @in_p, @out_p, @err_p = nil, nil, nil
      Dir["#{tmp}/{gems/*,*}"].each do |dir|
        next if %(base remote1 gems rubygems).include?(File.basename(dir))
        if ENV['BUNDLER_SUDO_TESTS']
          `sudo rm -rf #{dir}`
        else
          FileUtils.rm_rf(dir)
        end
      end
      FileUtils.mkdir_p(tmp)
      FileUtils.mkdir_p(home)
      Bundler.send(:remove_instance_variable, :@settings) if Bundler.send(:instance_variable_defined?, :@settings)
    end

    attr_reader :out, :err, :exitstatus

    def in_app_root(&blk)
      Dir.chdir(bundled_app, &blk)
    end

    def in_app_root2(&blk)
      Dir.chdir(bundled_app2, &blk)
    end

    def in_app_root_custom(root, &blk)
      Dir.chdir(root, &blk)
    end

    def run(cmd, *args)
      opts = args.last.is_a?(Hash) ? args.pop : {}
      expect_err = opts.delete(:expect_err)
      env = opts.delete(:env)
      groups = args.map {|a| a.inspect }.join(", ")
      setup = "require 'rubygems' ; require 'bundler' ; Bundler.setup(#{groups})\n"
      @out = ruby(setup + cmd, :expect_err => expect_err, :env => env)
    end

    def load_error_run(ruby, name, *args)
      cmd = <<-RUBY
        begin
          #{ruby}
        rescue LoadError => e
          $stderr.puts "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
        end
      RUBY
      opts = args.last.is_a?(Hash) ? args.pop : {}
      opts.merge!(:expect_err => true)
      args += [opts]
      run(cmd, *args)
    end

    def lib
      File.expand_path('../../../lib', __FILE__)
    end

    def bundle(cmd, options = {})
      expect_err = options.delete(:expect_err)
      sudo       = "sudo" if options.delete(:sudo)
      options["no-color"] = true unless options.key?("no-color") || %w(exec conf).include?(cmd.to_s[0..3])

      bundle_bin = File.expand_path('../../../bin/bundle', __FILE__)

      requires = options.delete(:requires) || []
      requires << File.expand_path('../fakeweb/'+options.delete(:fakeweb)+'.rb', __FILE__) if options.key?(:fakeweb)
      requires << File.expand_path('../artifice/'+options.delete(:artifice)+'.rb', __FILE__) if options.key?(:artifice)
      requires_str = requires.map{|r| "-r#{r}"}.join(" ")

      env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}'"}.join(" ")
      args = options.map do |k, v|
        v == true ? " --#{k}" : " --#{k} #{v}" if v
      end.join

      cmd = "#{env} #{sudo} #{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin} #{cmd}#{args}"
      sys_exec(cmd, expect_err){|i| yield i if block_given? }
    end

    def bundle_ruby(options = {})
      expect_err = options.delete(:expect_err)
      options["no-color"] = true unless options.key?("no-color")

      bundle_bin = File.expand_path('../../../bin/bundle_ruby', __FILE__)

      requires = options.delete(:requires) || []
      requires << File.expand_path('../fakeweb/'+options.delete(:fakeweb)+'.rb', __FILE__) if options.key?(:fakeweb)
      requires << File.expand_path('../artifice/'+options.delete(:artifice)+'.rb', __FILE__) if options.key?(:artifice)
      requires_str = requires.map{|r| "-r#{r}"}.join(" ")

      env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}' "}.join
      cmd = "#{env}#{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin}"

      sys_exec(cmd, expect_err){|i| yield i if block_given? }
    end

    def ruby(ruby, options = {})
      expect_err = options.delete(:expect_err)
      env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}' "}.join
      ruby.gsub!(/["`\$]/) {|m| "\\#{m}" }
      lib_option = options[:no_lib] ? "" : " -I#{lib}"
      sys_exec(%{#{env}#{Gem.ruby}#{lib_option} -e "#{ruby}"}, expect_err)
    end

    def load_error_ruby(ruby, name, opts = {})
      cmd = <<-R
        begin
          #{ruby}
        rescue LoadError => e
          $stderr.puts "ZOMG LOAD ERROR"# if e.message.include?("-- #{name}")
        end
      R
      ruby(cmd, opts.merge(:expect_err => true))
    end

    def gembin(cmd)
      lib = File.expand_path("../../../lib", __FILE__)
      old, ENV['RUBYOPT'] = ENV['RUBYOPT'], "#{ENV['RUBYOPT']} -I#{lib}"
      cmd = bundled_app("bin/#{cmd}") unless cmd.to_s.include?("/")
      sys_exec(cmd.to_s)
    ensure
      ENV['RUBYOPT'] = old
    end

    def sys_exec(cmd, expect_err = false)
      Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr|
        @in_p, @out_p, @err_p = stdin, stdout, stderr

        yield @in_p if block_given?
        @in_p.close

        @out = @out_p.read_available_bytes.strip
        @err = @err_p.read_available_bytes.strip
        @exitstatus = wait_thr && wait_thr.value.exitstatus
      end

      puts @err unless expect_err || @err.empty? || !$show_err
      @out
    end

    def config(config = nil, path = bundled_app('.bundle/config'))
      return YAML.load_file(path) unless config
      FileUtils.mkdir_p(File.dirname(path))
      File.open(path, 'w') do |f|
        f.puts config.to_yaml
      end
      config
    end

    def global_config(config = nil)
      config(config, home(".bundle/config"))
    end

    def create_file(*args)
      path = bundled_app(args.shift)
      path = args.shift if args.first.is_a?(Pathname)
      str  = args.shift || ""
      path.dirname.mkpath
      File.open(path.to_s, 'w') do |f|
        f.puts strip_whitespace(str)
      end
    end

    def gemfile(*args)
      create_file("Gemfile", *args)
    end

    def lockfile(*args)
      create_file("Gemfile.lock", *args)
    end

    def strip_whitespace(str)
      # Trim the leading spaces
      spaces = str[/\A\s+/, 0] || ""
      str.gsub(/^#{spaces}/, '')
    end

    def install_gemfile(*args)
      gemfile(*args)
      opts = args.last.is_a?(Hash) ? args.last : {}
      opts[:retry] ||= 0
      bundle :install, opts
    end

    def install_gems(*gems)
      gems.each do |g|
        path = "#{gem_repo1}/gems/#{g}.gem"

        raise "OMG `#{path}` does not exist!" unless File.exist?(path)

        gem_command :install, "--no-rdoc --no-ri --ignore-dependencies #{path}"
      end
    end

    alias install_gem install_gems

    def with_gem_path_as(path)
      gem_home, gem_path = ENV['GEM_HOME'], ENV['GEM_PATH']
      ENV['GEM_HOME'], ENV['GEM_PATH'] = path.to_s, path.to_s
      yield
    ensure
      ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path
    end

    def with_path_as(path)
      old_path = ENV['PATH']
      ENV['PATH'] = "#{path}:#{ENV['PATH']}"
      yield
    ensure
      ENV['PATH'] = old_path
    end

    def break_git!
      FileUtils.mkdir_p(tmp("broken_path"))
      File.open(tmp("broken_path/git"), "w", 0755) do |f|
        f.puts "#!/usr/bin/env ruby\nSTDERR.puts 'This is not the git you are looking for'\nexit 1"
      end

      ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}"
    end

    def fake_man!
      FileUtils.mkdir_p(tmp("fake_man"))
      File.open(tmp("fake_man/man"), "w", 0755) do |f|
        f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n"
      end

      ENV["PATH"] = "#{tmp("fake_man")}:#{ENV["PATH"]}"
    end

    def kill_path!
      ENV["PATH"] = ""
    end

    def system_gems(*gems)
      gems = gems.flatten

      FileUtils.rm_rf(system_gem_path)
      FileUtils.mkdir_p(system_gem_path)

      Gem.clear_paths

      gem_home, gem_path, path = ENV['GEM_HOME'], ENV['GEM_PATH'], ENV['PATH']
      ENV['GEM_HOME'], ENV['GEM_PATH'] = system_gem_path.to_s, system_gem_path.to_s

      install_gems(*gems)
      if block_given?
        begin
          yield
        ensure
          ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path
          ENV['PATH'] = path
        end
      end
    end

    def realworld_system_gems(*gems)
      gems = gems.flatten

      FileUtils.rm_rf(system_gem_path)
      FileUtils.mkdir_p(system_gem_path)

      Gem.clear_paths

      gem_home, gem_path, path = ENV['GEM_HOME'], ENV['GEM_PATH'], ENV['PATH']
      ENV['GEM_HOME'], ENV['GEM_PATH'] = system_gem_path.to_s, system_gem_path.to_s

      gems.each do |gem|
        gem_command :install, "--no-rdoc --no-ri #{gem}"
      end
      if block_given?
        begin
          yield
        ensure
          ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path
          ENV['PATH'] = path
        end
      end
    end

    def cache_gems(*gems)
      gems = gems.flatten

      FileUtils.rm_rf("#{bundled_app}/vendor/cache")
      FileUtils.mkdir_p("#{bundled_app}/vendor/cache")

      gems.each do |g|
        path = "#{gem_repo1}/gems/#{g}.gem"
        raise "OMG `#{path}` does not exist!" unless File.exist?(path)
        FileUtils.cp(path, "#{bundled_app}/vendor/cache")
      end
    end

    def simulate_new_machine
      system_gems []
      FileUtils.rm_rf default_bundle_path
      FileUtils.rm_rf bundled_app('.bundle')
    end

    def simulate_platform(platform)
      old, ENV['BUNDLER_SPEC_PLATFORM'] = ENV['BUNDLER_SPEC_PLATFORM'], platform.to_s
      yield if block_given?
    ensure
      ENV['BUNDLER_SPEC_PLATFORM'] = old if block_given?
    end

    def simulate_ruby_engine(engine, version = "1.6.0")
      return if engine == local_ruby_engine

      old, ENV['BUNDLER_SPEC_RUBY_ENGINE'] = ENV['BUNDLER_SPEC_RUBY_ENGINE'], engine
      old_version, ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'] = ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'], version
      yield if block_given?
    ensure
      ENV['BUNDLER_SPEC_RUBY_ENGINE'] = old if block_given?
      ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'] = old_version if block_given?
    end

    def simulate_bundler_version(version)
      old, ENV['BUNDLER_SPEC_VERSION'] = ENV['BUNDLER_SPEC_VERSION'], version.to_s
      yield if block_given?
    ensure
      ENV['BUNDLER_SPEC_VERSION'] = old if block_given?
    end

    def revision_for(path)
      Dir.chdir(path) { `git rev-parse HEAD`.strip }
    end

    def capture_output
      fake_stdout = StringIO.new
      actual_stdout = $stdout
      $stdout = fake_stdout
      yield
      fake_stdout.rewind
      fake_stdout.read
    ensure
      $stdout = actual_stdout
    end

    def with_read_only(pattern)
      chmod = lambda do |dirmode, filemode|
        lambda do |f|
          mode = File.directory?(f) ? dirmode : filemode
          File.chmod(mode, f)
        end
      end

      Dir[pattern].each(&chmod[0555, 0444])
      yield
    ensure
      Dir[pattern].each(&chmod[0755, 0644])
    end

    def process_file(pathname)
      changed_lines = pathname.readlines.map do |line|
        yield line
      end
      File.open(pathname, 'w') { |file| file.puts(changed_lines.join) }
    end

    def with_env_vars(env_hash, &block)
      current_values = {}
      env_hash.each do |k,v|
        current_values[k] = v
        ENV[k] = v
      end
      block.call if block_given?
      env_hash.each do |k,v|
        ENV[k] = current_values[k]
      end
    end
  end
end
