require 'capistrano/errors'

module Capistrano
  module Deploy
    class RemoteDependency
      attr_reader :configuration
      attr_reader :hosts

      def initialize(configuration)
        @configuration = configuration
        @success = true
        @hosts = nil
      end

      def directory(path, options={})
        @message ||= "`#{path}' is not a directory"
        try("test -d #{path}", options)
        self
      end

      def file(path, options={})
        @message ||= "`#{path}' is not a file"
        try("test -f #{path}", options)
        self
      end

      def writable(path, options={})
        @message ||= "`#{path}' is not writable"
        try("test -w #{path}", options)
        self
      end

      def command(command, options={})
        @message ||= "`#{command}' could not be found in the path"
        try("which #{command}", options)
        self
      end

      def gem(name, version, options={})
        @message ||= "gem `#{name}' #{version} could not be found"
        gem_cmd = configuration.fetch(:gem_command, "gem")
        try("#{gem_cmd} specification --version '#{version}' #{name} 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }'", options)
        self
      end

      def deb(name, version, options={})
        @message ||= "package `#{name}' #{version} could not be found"
        try("dpkg -s #{name} | grep '^Version: #{version}'", options)
        self
      end

      def rpm(name, version, options={})
        @message ||= "package `#{name}' #{version} could not be found"
        try("rpm -q #{name} | grep '#{version}'", options)
        self
      end

      def match(command, expect, options={})
        expect = Regexp.new(Regexp.escape(expect.to_s)) unless expect.is_a?(Regexp)

        output_per_server = {}
        try("#{command} ", options) do |ch, stream, out|
          output_per_server[ch[:server]] ||= ''
          output_per_server[ch[:server]] += out
        end

        # It is possible for some of these commands to return a status != 0
        # (for example, rake --version exits with a 1). For this check we
        # just care if the output matches, so we reset the success flag.
        @success = true

        errored_hosts = []
        output_per_server.each_pair do |server, output|
          next if output =~ expect
          errored_hosts << server
        end

        if errored_hosts.any?
          @hosts = errored_hosts.join(', ')
          output = output_per_server[errored_hosts.first]
          @message = "the output #{output.inspect} from #{command.inspect} did not match #{expect.inspect}"
          @success = false
        end

        self
      end

      def or(message)
        @message = message
        self
      end

      def pass?
        @success
      end

      def message
        s = @message.dup
        s << " (#{@hosts})" if @hosts
        s
      end

    private

      def try(command, options)
        return unless @success # short-circuit evaluation
        configuration.invoke_command(command, options) do |ch,stream,out|
          warn "#{ch[:server]}: #{out}" if stream == :err
          yield ch, stream, out if block_given?
        end
      rescue Capistrano::CommandError => e
        @success = false
        @hosts = e.hosts.join(', ')
      end
    end
  end
end
