module BBLib
  module Delegator
    def self.included(base)
      base.extend(ClassMethods)
      base.send(:attr_ary, :instance_delegates)
    end

    def delegates
      (instance_delegates + self.class.delegates).uniq
    end

    protected

    def method_missing(method, *args, &block)
      delegates.each do |delegate|
        case delegate
        when Symbol
          next unless respond_to?(delegate) && method(delegate).arity == 0
          object = send(delegate)
          next unless object.respond_to?(method)
          return object.send(method, *args, &block)
        else
          next unless delegate.respond_to?(method)
          return delegate.send(method, *args, &block)
        end
      end
      super
    end

    def respond_to_missing?(method, include_private = false)
      return super if self.class.delegate_fast
      super || delegates.any? do |delegate|
        next if delegate == self # Protection from recursion
        case delegate
        when Symbol
          self.method(delegate)&.arity == 0 &&
          send(delegate).respond_to?(method)
        else
          delegate.respond_to?(method)
        end
      end
    end

    def delegate_to(*mthds)
      mthds.flatten.each do |method|
        next if instance_delegates.include?(method)
        instance_delegates << method
      end
      true
    end

    module ClassMethods
      # When turned on the respond_to_missing method is left unchanged.
      # This GREATLY speeds up the instantiation of classes with lots of
      # calls to respond_to?
      def delegate_fast(*args)
        return @delegate_fast ||= _ancestor_delegate_fast if args.empty?
        @delegate_fast = args.first ? true : false
      end

      def delegate_to(*mthds)
        mthds.flatten.each do |method|
          next if delegates.include?(method)
          delegates << method
        end
        true
      end

      def _ancestor_delegate_fast
        ancestors.reverse.find do |anc|
          next unless anc.respond_to?(:delegate_fast)
          return anc.delegate_fast
        end
        true
      end

      def delegates
        @delegates ||= ancestor_delegates
      end

      def ancestor_delegates
        ancestors.reverse.flat_map do |anc|
          next if anc == self || !anc.respond_to?(:delegates)
          anc.delegates
        end.compact.uniq
      end
    end
  end
end