require 'ae/assertion'
require 'ae/basic_object'

module AE

  # Assertor is the underlying class of the whole system. It implements
  # the flutent assertion notation.
  #
  # An Assertor is an Assertion Functor. A Functor is a succinct name for what
  # is also known as Higher Order Function. In other words, it is a function
  # that acts on a function. It is very similiar  to a delegator in most
  # respects, but is conditioned on the operation applied, rather then simply
  # passing-off to an alternate reciever.
  #
  class Assertor < AE::BasicObject

    # Initial settings of assertion counts.
    ZERO_COUNTS = {:total=>0,:pass=>0,:fail=>0}

    # Initialize assertion counts global variable.
    $assertion_counts = ZERO_COUNTS.dup

    # Returns Hash used to track assertion counts.
    def self.counts
      $assertion_counts
    end

    # Reset assertion counts.
    #
    # reset - Hash which can be used to set counts manually (optional).
    #
    # Returns the Hash of previous counts.
    def self.recount(reset=nil)
      old_counts = counts.dup
      if reset
        reset.each do |type, value|
          counts[type.to_sym] = value
        end
      else
        counts.replace(ZERO_COUNTS.dup)
      end
      return old_counts
    end

    # Increment assertion counts. If +pass+ is +true+ then +:total+
    # and +:pass+ are increased. If +pass+ if +false+ then +:total+ 
    # and +:fail+ are incremented.
    def self.increment_counts(pass)
      counts[:total] += 1
      if pass
        counts[:pass] += 1
      else
        counts[:fail] += 1
      end
      return counts
    end

    # Basic assertion. This method by-passes all the Assertor fluent
    # constructs and performs the underlying assertion procedure. It
    # is used by Assertor as the end call of an assertion.
    def self.assert(pass, error=nil, negated=nil, backtrace=nil)
      pass = negated ^ !!pass
      increment_counts(pass)
      if !pass
        backtrace = backtrace || caller
        raise_assertion(error, negated, backtrace)
      end
      return pass
    end

    # The intent of the method is to raise an assertion failure
    # class that the test framework supports.
    def self.raise_assertion(error, negated, backtrace=nil)
      if not Exception === error
        error = assertion_error.new(error)
      end
      error.set_negative(negated)
      error.set_backtrace(backtrace || caller)
      error.set_assertion(true)
      fail error
    end

    # Returns the Exception class to be raised when an assertion fails.
    def self.assertion_error
      ::Assertion
    end

    # NOT HAPPENING
    ## Is ::Assay defined. This is used for integration of the Assay library.
    #def self.assay?
    #  @_assay ||= defined?(::Assay)
    #end

    #
    #def self.message(sym, neg, *args, &blk)
    #  if method = Message.lookup(sym) 
    #    method = "non_#{method}" if neg
    #    Message.send(method, *args, &blk)
    #  else
    #    nil
    #  end
    #end

    #
    if ::RUBY_VERSION >= '1.9'
      eval "private :==, :!, :!="  # using eval here b/c it's a syntax error in 1.8-
    end

    # New Assertor.
    #
    def initialize(delegate, opts={}) #, backtrace)
      @delegate  = delegate
      @message   = opts[:message]
      @backtrace = opts[:backtrace] || caller #[1..-1]
      @negated   = !!opts[:negated]
    end

    # Negate the meaning of the assertion.
    #
    #--
    # TODO: Should this return a new Assertor instead of in place negation?
    #++
    def not(msg=nil)
      @negated = !@negated
      @message = msg if msg
      self
    end

    # Internal assert, provides all functionality associated
    # with external #assert method. (See Assert#assert)
    #
    # NOTE: I'm calling YAGNI on using extra arguments to pass
    # to the block. The interface is much nicer if a macro is
    # created to handle any neccessry arguments. Eg.
    #
    #   assert something(parameter)
    #
    # instead of
    #
    #   assert something, parameter
    #
    # Returns +true+ or +false+ based on assertions success.
    #--
    # The use of #to_proc and #matches? as sepcial cases is not
    # a robust solution.
    #++
    def assert(*args, &block)
      return self if args.empty? && !block

      target = block || args.shift
      error  = nil

      # Lambda
      if ::Proc === target || target.respond_to?(:to_proc)
        block  = target.to_proc
        match  = args.shift
        result = block.arity > 0 ? block.call(@delegate) : block.call
        if match
          pass  = (match == result)
          error = @message || "#{match.inspect} == #{result.inspect}"
        else
          pass  = result
          error = @message || block.inspect  # "#{result.inspect}"
        end

      # Matcher
      elsif target.respond_to?(:matches?) # Matchers
        pass  = target.matches?(@delegate)
        error = @message || matcher_message(target) #|| target.inspect
        if target.respond_to?(:exception)
          #error_class = target.failure_class
          error = target.exception #(:backtrace=>@backtrace, :negated=>@negated)
        end

      # Truthiness
      else
        pass  = target     # truthiness
        error = args.shift # optional message for TestUnit compatiability
      end

      __assert__(pass, error)
    end

    # Internal expect, provides all functionality associated
    # with external #expect method. (See Expect#expect)
    #
    #--
    # TODO: Should we deprecate the receiver matches in favor of #expected ?
    # In other words, should the <code>|| @delegate</code> be dropped?
    #++
    def expect(*args, &block)
      return self if args.empty? && !block  # same as #assert

      target = block || args.shift
      error  = nil

      # Lambda
      if ::Proc === target #|| target.respond_to?(:to_proc)
        #block = target.to_proc
        match = args.shift || @delegate
        if exception?(match)
          $DEBUG, debug = false, $DEBUG  # b/c it always spits-out a NameError
          begin
            block.arity > 0 ? block.call(@delegate) : block.call
            pass  = false
            error = "#{match} not raised"
          rescue match => error
            pass  = true
            error = "#{match} raised"
          rescue ::Exception => error
            pass  = false
            error = "#{match} expected but #{error.class} was raised"
          ensure
            $DEBUG = debug
          end
        else
          result = block.arity > 0 ? block.call(@delegte) : block.call
          pass   = (match === result)
          error  = @message || "#{match.inspect} === #{result.inspect}"
        end

      # Matcher
      elsif target.respond_to?(:matches?)
        pass  = target.matches?(@delegate)
        error = @message || matcher_message(target) #|| target.inspect
        if target.respond_to?(:exception)
          #error_class = target.failure_class
          error = target.exception #failure(:backtrace=>@backtrace, :negated=>@negated)
        end

      # Case Equals
      else
        pass  = (target === @delegate)
        error = @message || "#{target.inspect} === #{@delegate.inspect}"
      end

      __assert__(pass, error)
    end

    #
    def flunk(message=nil, backtrace=nil)
      __assert__(false, message || @message)
    end

    # Ruby seems to have a quark in it's implementation whereby
    # this must be defined explicitly, otherwise it somehow
    # skips #method_missing.
    def =~(match)
      method_missing(:"=~", match)
    end

    #
    def send(op, *a, &b)
      method_missing(op, *a, &b)
    end

    #
    def inspect
      @delegate.inspect
    end

    private

    # Is the +object+ an Exception or an instance of one?
    #--
    # TODO: Should we use a more libreral determination of exception.
    # e.g. <code>respond_to?(:exception)</code>.
    #++
    def exception?(object)
      ::Exception === object or ::Class === object and object.ancestors.include?(::Exception)
    end

    # Converts a missing method into an Assertion.
    #
    # TODO: In future should probably be `@delegate.public_send(sym, *a, &b)`.
    def method_missing(sym, *args, &block)
      error = @message || compare_message(sym, *args, &block) || generic_message(sym, *args, &block)

      pass = @delegate.__send__(sym, *args, &block)

      __assert__(pass, error)
    end


    # Simple assert.
    #--
    # TODO: Can the handling of the message be simplified/improved?
    #++
    def __assert__(pass, error=nil)
      Assertor.assert(pass, error, @negated, @backtrace)
    end

    #
    def matcher_message(matcher)
      if @negated
        if matcher.respond_to?(:negative_failure_message)
          return matcher.failure_message
        end
      else
        if matcher.respond_to?(:failure_message)
          return matcher.failure_message
        end
      end
      return nil
    end

    COMPARISON_OPERATORS = { :"==" => :"!=" }

    # Message to use when making a comparion assertion.
    def compare_message(operator, *args, &blk)
      return nil unless COMPARISON_OPERATORS.key?(operator)
      prefix = ""
      a, b   = @delegate, args.first
      if @negated
        op = COMPARISON_OPERATORS[operator]
        if op
          operator = op
        else
          prefix   = "NOT "
        end
      end
      if a.size > 13 or b.size > 13
        diff = ANSI::Diff.new(a,b)
        prefix + "a #{operator} b\na) " + diff.diff1 + "\nb) " + diff.diff2
      else
        prefix + "#{a.inspect} #{operator} #{b.inspect}"
      end
    end

    # Puts together a suitable error message.
    #
    def generic_message(op, *a, &b)
      inspection = @delegate.send(:inspect)
      if @negated
        "! #{inspection} #{op} #{a.collect{|x| x.inspect}.join(',')}"
      else
        "#{inspection} #{op} #{a.collect{|x| x.inspect}.join(',')}"
      end
      #self.class.message(m)[@delegate, *a] )
    end

  end

end

# DO WE MAKE THESE EXCEPTIONS?
#class BasicObject
#  def assert
#  end
#end

# Copyright (c) 2008,2009 Thomas Sawyer