require 'timeout'

module Compass::CommandLineHelper
  def compass(*arguments)
    options = arguments.last.is_a?(Hash) ? arguments.pop : {}
    options[:wait] = 0.25
    if block_given?
      responder = Responder.new
      yield responder
      IO.popen("-", "r+") do |io|
        if io
          #parent process
          output = ""
          eof_at = nil
          while !eof_at || (Time.now - eof_at < options[:wait])
            if io.eof?
              eof_at ||= Time.now
              sleep 0.1
            else
              eof_at = nil
              timeout(1) do
                partial_output = io.readpartial(1024)
                # puts "))))#{partial_output}((((("
                output << partial_output
              end
              prompt = output.split("\n").last.strip
              if response = responder.response_for(prompt)
                io.puts response
                io.flush
              end
            end
          end
          responder.assert_required_responses!
          @last_result = decolorize(output)
        else
          #child process
          execute *arguments
        end
      end
    else
      @last_error = capture_warning do
        @last_result = decolorize(capture_output do
          @last_exit_code = execute *arguments
        end)
      end
    end
  rescue Timeout::Error
    fail "Read from child process timed out"
  end

  def decolorize(str)
    str.gsub(/\e\[\d+m/,'')
  end

  class Responder
    Response = Struct.new(:prompt, :text, :required, :responded)
    def initialize
      @responses = []
    end
    def respond_to(prompt, options = {})
      @responses << Response.new(prompt, options[:with], options[:required])
    end
    def response_for(prompt)
      response = @responses.detect do |r|
        case r.prompt
        when Regexp
          prompt =~ r.prompt
        when String
          r.prompt == prompt
        end
      end
      if response
        response.responded = true
        response.text
      end
    end
    def assert_required_responses!
      @responses.each do |response|
        if response.required && !response.responded
          raise "Prompt not encountered: \"#{response.prompt}\""
        end
      end
    end
  end

  def assert_action_performed(action, path)
    actions_found = []
    @last_result.split("\n").each do |line|
      line = line.split
      return if line.first == action.to_s && line.last == path
      actions_found << line.first if line.last == path
    end
    message = "Action #{action.inspect} was not performed on: #{path}."
    message += "The following actions were performed: #{actions_found.map{|a|a.inspect}.join(", ")}" if actions_found.any?
    # puts @last_result
    fail message
  end

  def within_tmp_directory(dir = "tmp")
    d = absolutize(dir)
    FileUtils.mkdir_p(d)
    Dir.chdir(d) do
      yield
    end
  ensure
    FileUtils.rm_rf(d)
  end

  def execute(*arguments)
    exit_code = Compass::Exec::SubCommandUI.new(arguments).run!
    # fail "Command Failed with exit code: #{exit_code}" unless exit_code == 0
    exit_code
  end
end
