class Shoulda::Matchers::Independent::DelegateMethodMatcher

@private

def allow_nil

def allow_nil
  @expects_to_allow_nil_delegate_object = true
  self
end

def as(delegate_method)

def as(delegate_method)
  @delegate_method = delegate_method
  self
end

def build_delegating_method_prefix(prefix)

def build_delegating_method_prefix(prefix)
  case prefix
  when true, nil then delegate_object_reader_method
  else prefix
  end
end

def call_delegating_method_with_delegate_method_returning(value)

def call_delegating_method_with_delegate_method_returning(value)
  register_subject_double_collection_to(value)
  Doublespeak.with_doubles_activated do
    subject.public_send(delegating_method, *delegated_arguments)
  end
end

def calls_on_delegate_object

def calls_on_delegate_object
  delegate_object.calls
end

def calls_to_delegate_method

def calls_to_delegate_method
  delegate_object.calls_to(delegate_method)
end

def class_or_instance_method_indicator

def class_or_instance_method_indicator
  if subject_is_a_class?
    '.'
  else
    '#'
  end
end

def class_under_test

def class_under_test
  if subject_is_a_class?
    subject
  else
    subject.class
  end
end

def delegate_object_received_call?

def delegate_object_received_call?
  calls_to_delegate_method.any?
end

def delegate_object_received_call_with_delegated_arguments?

def delegate_object_received_call_with_delegated_arguments?
  calls_to_delegate_method.any? do |call|
    call.args == delegated_arguments
  end
end

def description

def description
  string =
    "delegate #{formatted_delegating_method_name} to the " +
    "#{formatted_delegate_object_reader_method_name} object"
  if expects_private_delegation?
    string << ' privately'
  end
  if delegated_arguments.any?
    string << " passing arguments #{delegated_arguments.inspect}"
  end
  if delegate_method != delegating_method
    string << " as #{formatted_delegate_method}"
  end
  if expects_to_allow_nil_delegate_object?
    string << ', allowing '
    string << formatted_delegate_object_reader_method_name
    string << ' to return nil'
  end
  string
end

def ensure_delegate_object_has_been_specified!

def ensure_delegate_object_has_been_specified!
  if delegate_object_reader_method.to_s.empty?
    raise DelegateObjectNotSpecified
  end
end

def expects_private_delegation?

def expects_private_delegation?
  @expects_private_delegation
end

def expects_to_allow_nil_delegate_object?

def expects_to_allow_nil_delegate_object?
  @expects_to_allow_nil_delegate_object
end

def failed_to_allow_nil_delegate_object?

def failed_to_allow_nil_delegate_object?
  expects_to_allow_nil_delegate_object? &&
    !@subject_handled_nil_delegate_object
end

def failed_to_handle_private_delegation?

def failed_to_handle_private_delegation?
  expects_private_delegation? &&
    !@subject_handled_private_delegation
end

def failure_message

def failure_message
  message = "Expected #{class_under_test} to #{description}.\n\n"
  if failed_to_allow_nil_delegate_object? || failed_to_handle_private_delegation?
    message << formatted_delegating_method_name(include_module: true)
    message << ' did delegate to '
    message << formatted_delegate_object_reader_method_name
  end
  if failed_to_allow_nil_delegate_object?
    message << ' when it was non-nil, but it failed to account '
    message << 'for when '
    message << formatted_delegate_object_reader_method_name
    message << ' *was* nil.'
  elsif failed_to_handle_private_delegation?
    message << ", but 'private: true' is missing."
  else
    message << 'Method calls sent to '
    message << formatted_delegate_object_reader_method_name(
      include_module: true,
    )
    message << ": #{formatted_calls_on_delegate_object}"
  end
  Shoulda::Matchers.word_wrap(message)
end

def failure_message_when_negated

def failure_message_when_negated
  "Expected #{class_under_test} not to #{description}, but it did."
end

def formatted_calls_on_delegate_object

def formatted_calls_on_delegate_object
  String.new.tap do |string|
    if calls_on_delegate_object.any?
      string << "\n\n"
      calls_on_delegate_object.each_with_index do |call, i|
        name = call.method_name
        args = call.args.map(&:inspect).join(', ')
        string << "#{i + 1}) #{name}(#{args})\n"
      end
    else
      string << ' (none)'
    end
    string.rstrip!
  end
end

def formatted_delegate_method(options = {})

def formatted_delegate_method(options = {})
  formatted_method_name_for(delegate_method, options)
end

def formatted_delegate_object_reader_method_name(options = {})

def formatted_delegate_object_reader_method_name(options = {})
  formatted_method_name_for(delegate_object_reader_method, options)
end

def formatted_delegating_method_name(options = {})

def formatted_delegating_method_name(options = {})
  formatted_method_name_for(delegating_method, options)
end

def formatted_method_name_for(method_name, options)

def formatted_method_name_for(method_name, options)
  possible_class_under_test(options) +
    class_or_instance_method_indicator +
    method_name.to_s
end

def in_context(context)

def in_context(context)
  @context = MatcherContext.new(context)
  self
end

def initialize(delegating_method)

def initialize(delegating_method)
  @delegating_method = delegating_method
  @delegate_method = @delegating_method
  @delegate_object = Doublespeak::ObjectDouble.new
  @context = nil
  @subject = nil
  @delegate_object_reader_method = nil
  @delegated_arguments = []
  @expects_to_allow_nil_delegate_object = false
  @expects_private_delegation = false
end

def matches?(subject)

def matches?(subject)
  @subject = subject
  ensure_delegate_object_has_been_specified!
  subject_has_delegating_method? &&
    subject_has_delegate_object_reader_method? &&
    subject_delegates_to_delegate_object_correctly? &&
    subject_handles_nil_delegate_object? &&
    subject_handles_private_delegation?
end

def possible_class_under_test(options)

def possible_class_under_test(options)
  if options[:include_module]
    class_under_test.to_s
  else
    ''
  end
end

def privately_call_delegating_method_with_delegate_method_returning(value)

def privately_call_delegating_method_with_delegate_method_returning(value)
  register_subject_double_collection_to(value)
  Doublespeak.with_doubles_activated do
    subject.__send__(delegating_method, *delegated_arguments)
  end
end

def register_subject_double_collection_to(returned_value)

def register_subject_double_collection_to(returned_value)
  double_collection =
    Doublespeak.double_collection_for(subject.singleton_class)
  double_collection.register_stub(delegate_object_reader_method).
    to_return(returned_value)
end

def subject

def subject
  @subject
end

def subject_delegates_to_delegate_object_correctly?

def subject_delegates_to_delegate_object_correctly?
  if expects_private_delegation?
    privately_call_delegating_method_with_delegate_method_returning(delegate_object)
  else
    call_delegating_method_with_delegate_method_returning(delegate_object)
  end
  if delegated_arguments.any?
    delegate_object_received_call_with_delegated_arguments?
  else
    delegate_object_received_call?
  end
end

def subject_handles_nil_delegate_object?

def subject_handles_nil_delegate_object?
  @subject_handled_nil_delegate_object =
    if expects_to_allow_nil_delegate_object?
      begin
        call_delegating_method_with_delegate_method_returning(nil)
        true
      rescue Module::DelegationError
        false
      rescue NoMethodError => e
        if e.message =~ /undefined method [`']#{delegate_method}[`'] for nil/
          false
        else
          raise e
        end
      end
    else
      true
    end
end

def subject_handles_private_delegation?

def subject_handles_private_delegation?
  @subject_handled_private_delegation =
    if expects_private_delegation?
      begin
        call_delegating_method_with_delegate_method_returning(delegate_object)
        true
      rescue Module::DelegationError
        false
      rescue NoMethodError => e
        if e.message =~ /private method [`']#{delegating_method}[`'] called for/
          true
        else
          raise e
        end
      end
    else
      true
    end
end

def subject_has_delegate_object_reader_method?

def subject_has_delegate_object_reader_method?
  subject.respond_to?(delegate_object_reader_method, true)
end

def subject_has_delegating_method?

def subject_has_delegating_method?
  if expects_private_delegation?
    !subject.respond_to?(delegating_method) && subject.respond_to?(delegating_method, true)
  else
    subject.respond_to?(delegating_method)
  end
end

def subject_is_a_class?

def subject_is_a_class?
  if @subject
    @subject.is_a?(Class)
  else
    context.subject_is_a_class?
  end
end

def to(delegate_object_reader_method)

def to(delegate_object_reader_method)
  @delegate_object_reader_method = delegate_object_reader_method
  self
end

def with_arguments(*arguments)

def with_arguments(*arguments)
  @delegated_arguments = arguments
  self
end

def with_prefix(prefix = nil)

def with_prefix(prefix = nil)
  @delegating_method =
    :"#{build_delegating_method_prefix(prefix)}_#{delegate_method}"
  delegate_method
  self
end

def with_private

def with_private
  @expects_private_delegation = true
  self
end