unless defined?(Bundler)
  $stderr.puts "You didn't run bundle exec did you? Try again: bundle exec rake test"
  exit 1
end
require 'fileutils'
require 'tmpdir'
require 'sprockets/helpers/rails_helper'

class Sass::Rails::TestCase < ActiveSupport::TestCase

  class ExecutionError < StandardError
    attr_accessor :output
    def initialize(message, output = nil)
      super(message)
      self.output = output
    end
    def message
      "#{super}\nOutput was:\n#{output}"
    end
  end

  module SilentError
    attr_accessor :output
    def message
      "#{super}\nOutput was:\n#{output}"
    end
  end

  protected

  def fixture_path(path)
    File.expand_path("../../fixtures/#{path}", __FILE__)
  end

  module TestAssetPaths
    attr_accessor :assets
  end

  def sprockets_render(project, filename)
    within_rails_app(project) do
      runcmd "ruby script/rails runner 'puts Rails.application.assets[#{filename.inspect}]'"
    end
  end

  def assert_file_exists(filename)
    assert File.exists?(filename), "could not find #{filename}. PWD=#{Dir.pwd}\nDid find: #{Dir.glob(File.dirname(filename)+"/*").join(", ")}"
  end

  def assert_not_output(match)
    assert_no_match match, $last_ouput
  end

  def assert_output(match)
    assert $last_ouput.to_s =~ match, "#{match} was not found in #{$last_ouput.inspect}"
  end

  # Copies a rails app fixture to a temp directory
  # and changes to that directory during the yield.
  #
  # Automatically changes back to the working directory
  # and removes the temp directory when done.
  def within_rails_app(name, without_gems = [], gem_options = $gem_options)
    sourcedir = File.expand_path("../../fixtures/#{name}", __FILE__)
    Dir.mktmpdir do |tmpdir|
      FileUtils.cp_r "#{sourcedir}/.", tmpdir
      Dir.chdir(tmpdir) do
        gem_options.each {|name, options| modify_gem_entry name, options}
        without_gems.each {|name| remove_gem name}
        FileUtils.rm("Gemfile.lock") if File.exist?("Gemfile.lock")
        runcmd "bundle install --verbose"
        yield tmpdir
      end
    end
  end

  def process_gemfile(gemfile = "Gemfile", &blk)
    gem_contents = File.readlines(gemfile)
    gem_contents.map!(&blk)
    gem_contents.compact!
    File.open(gemfile, "w") do |f|
      f.print(gem_contents.join(""))
    end
  end

  def modify_gem_entry(gemname, options, gemfile = "Gemfile")
    found = false
    process_gemfile(gemfile) do |line|
      if line =~ /gem *(["'])#{Regexp.escape(gemname)}\1/
        found = true
        gem_entry(gemname, options) + "\n"
      else
        line
      end
    end
    unless found
      File.open(gemfile, "a") do |f|
        f.print("\n#{gem_entry(gemname, options)}\n")
      end
    end
  end

  def gem_entry(gemname, options)
    entry = %Q{gem "#{gemname}", "~> #{options[:version]}"}
    entry += ", :path => #{options[:path].inspect}" if options[:path]
    entry
  end

  def remove_gem(gemname)
    process_gemfile(gemfile) do |line|
      line unless line =~ /gem *(["'])#{Regexp.escape(gemname)}\1/
    end
  end

  def silently
    output = StringIO.new
    $stderr, old_stderr = output, $stderr
    $stdout, old_stdout = output, $stdout
    begin
      yield
    rescue ExecutionError => e
      raise
    rescue => e
      e.extend(SilentError)
      e.output = output.string
      raise
    end
  ensure
    $stderr = old_stderr
    $stdout = old_stdout
  end

  # executes a system command
  # raises an error if it does not complete successfully
  # returns the output as a string if it does complete successfully
  def runcmd(cmd, working_directory = Dir.pwd, clean_env = true, gemfile = "Gemfile", env = {})
    # There's a bug in bundler where with_clean_env doesn't clear out the BUNDLE_GEMFILE environment setting
    # https://github.com/carlhuda/bundler/issues/1133
    env["BUNDLE_GEMFILE"] = "#{working_directory}/#{gemfile}" if clean_env
    todo = Proc.new do
      r, w = IO.pipe
      pid = Kernel.spawn(env, cmd, :out =>w , :err => w, :chdir => working_directory)
      w.close
      Process.wait
      output = r.read
      r.close
      unless $?.exitstatus == 0
        raise ExecutionError, "Command failed with exit status #{$?.exitstatus}: #{cmd}", output
      end
      $last_ouput = output
    end
    if clean_env
      Bundler.with_clean_env(&todo)
    else
      todo.call
    end
  end

end
