require 'rspec/matchers/dsl'

module RSpec
  module Matchers

    # @method be_true
    RSpec::Matchers.define :be_true do
      match do |actual|
        actual
      end
    end

    RSpec::Matchers.define :be_false do
      match do |actual|
        !actual
      end
    end

    RSpec::Matchers.define :be_nil do
      match do |actual|
        actual.nil?
      end

      failure_message_for_should do |actual|
        "expected: nil\n     got: #{actual.inspect}"
      end

      failure_message_for_should_not do
        "expected: not nil\n     got: nil"
      end
    end

    class Be
      include RSpec::Matchers::Pretty
      
      def initialize(*args, &block)
        @args = args
      end
      
      def matches?(actual)
        @actual = actual
        !!@actual
      end

      def failure_message_for_should
        "expected #{@actual.inspect} to evaluate to true"
      end
      
      def failure_message_for_should_not
        "expected #{@actual.inspect} to evaluate to false"
      end
      
      def description
        "be"
      end

      [:==, :<, :<=, :>=, :>, :===].each do |operator|
        define_method operator do |operand|
          BeComparedTo.new(operand, operator)
        end
      end

    private

      def args_to_s
        @args.empty? ? "" : parenthesize(inspected_args.join(', '))
      end
      
      def parenthesize(string)
        "(#{string})"
      end
      
      def inspected_args
        @args.collect{|a| a.inspect}
      end
      
      def expected_to_sentence
        split_words(@expected)
      end
      
      def args_to_sentence
        to_sentence(@args)
      end
        
    end

    class BeComparedTo < Be

      def initialize(operand, operator)
        @expected, @operator = operand, operator
        @args = []
      end

      def matches?(actual)
        @actual = actual
        @actual.__send__(@operator, @expected)
      end

      def failure_message_for_should
        "expected: #{@operator} #{@expected.inspect}\n     got: #{@operator.to_s.gsub(/./, ' ')} #{@actual.inspect}"
      end
      
      def failure_message_for_should_not
        message = <<-MESSAGE
'should_not be #{@operator} #{@expected}' not only FAILED,
it is a bit confusing.
          MESSAGE
          
        raise message << ([:===,:==].include?(@operator) ?
          "It might be more clearly expressed without the \"be\"?" :
          "It might be more clearly expressed in the positive?")
      end

      def description
        "be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
      end

    end

    class BePredicate < Be

      def initialize(*args, &block)
        @expected = parse_expected(args.shift)
        @args = args
        @block = block
      end
      
      def matches?(actual)
        @actual = actual
        begin
          return @result = actual.__send__(predicate, *@args, &@block)
        rescue NameError => predicate_missing_error
          "this needs to be here or rcov will not count this branch even though it's executed in a code example"
        end

        begin
          return @result = actual.__send__(present_tense_predicate, *@args, &@block)
        rescue NameError
          raise predicate_missing_error
        end
      end
      
      def failure_message_for_should
        "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
      end
      
      def failure_message_for_should_not
        "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
      end

      def description
        "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
      end

    private

      def predicate
        "#{@expected}?".to_sym
      end
      
      def present_tense_predicate
        "#{@expected}s?".to_sym
      end
      
      def parse_expected(expected)
        @prefix, expected = prefix_and_expected(expected)
        expected
      end

      def prefix_and_expected(symbol)
        symbol.to_s =~ /^(be_(an?_)?)(.*)/
        return $1, $3
      end

      def prefix_to_sentence
        split_words(@prefix)
      end

    end

    # @example
    #   actual.should be_true
    #   actual.should be_false
    #   actual.should be_nil
    #   actual.should be_[arbitrary_predicate](*args)
    #   actual.should_not be_nil
    #   actual.should_not be_[arbitrary_predicate](*args)
    #
    # Given true, false, or nil, will pass if actual value is true, false or
    # nil (respectively). Given no args means the caller should satisfy an if
    # condition (to be or not to be). 
    #
    # Predicates are any Ruby method that ends in a "?" and returns true or
    # false.  Given be_ followed by arbitrary_predicate (without the "?"),
    # RSpec will match convert that into a query against the target object.
    #
    # The arbitrary_predicate feature will handle any predicate prefixed with
    # "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_"
    # (e.g. be_empty), letting you choose the prefix that best suits the
    # predicate.
    def be(*args)
      args.empty? ?
        Matchers::Be.new : equal(*args)
    end

    # passes if target.kind_of?(klass)
    def be_a(klass)
      be_a_kind_of(klass)
    end
    
    alias_method :be_an, :be_a
  end
end