require 'facets/core/symbol/not'

class Module

  METHOD_TYPES = [ :inherited, :ancestors, :local,
                   :public, :private, :protected,
                   :all ]

  # Provides an improved method lookup routnine.
  # It returns a list of methods according to symbol(s) given.
  #
  # Recognized symbols are:
  #
  # * <tt>:public</tt>     include public methods
  # * <tt>:private</tt>    include private methods
  # * <tt>:protected</tt>  include protected methods
  # * <tt>:ancestors</tt>  include all ancestor's methods
  # * <tt>:inherited</tt>  (same as above)
  # * <tt>:local</tti>     include non-ancestor methods
  # * <tt>:all</tt>        include all of the above
  #
  # This method also uses the symbol-not system. So you can specify
  # the inverse of all of the above. For instance ~:public would mean
  # :private, :protected (see facets/core/symbol/not).
  #
  # If no symbol is given then :public, :local is assumed.
  # Unrecognized symbols raise an error.
  #
  #   module Demo
  #     def test
  #       puts("Hello World!")
  #     end
  #   end
  #
  #   Demo.instance_methods(:local)    #=> ['test']
  #
  # To maintain backward compatibility <tt>true</tt> as an intitial argument
  # is converted to :local, :ancestors (i.e. it includes both).
  #
  def instance_methods(*args)

    # for backward compatibility
    args << true if args.empty?
    case args[0]
    when TrueClass
      args.shift
      args << :ancestors
      args << :local
    when FalseClass
      args.shift
      args << :local
    end

    # default public, local
    args |= [:public] unless [:publix,:private,:protected,:all].any?{ |a| args.include?(a) }
    args |= [:ancestors,:local] unless [:ancestors,:inherited,:local,:all].any?{ |a| args.include?(a) }

    raise ArgumentError if args.any?{ |a| ! METHOD_TYPES.include?(a) }

    pos, neg = args.partition { |s| ! s.not? }

    m = []

    pos.each do |n|
      case n
      when :inherited, :ancestors
        m |= ( public_instance_methods( true ) - public_instance_methods( false ) )       if pos.include?(:public)
        m |= ( private_instance_methods( true ) - private_instance_methods( false ) )     if pos.include?(:private)
        m |= ( protected_instance_methods( true ) - protected_instance_methods( false ) ) if pos.include?(:protected)
      when :local
        m |= public_instance_methods( false )    if pos.include?(:public)
        m |= private_instance_methods( false )   if pos.include?(:private)
        m |= protected_instance_methods( false ) if pos.include?(:protected)
      when :all
        m |= public_instance_methods( true )
        m |= private_instance_methods( true )
        m |= protected_instance_methods( true )
      end
    end

    neg.each do |n|
      case n
      when :public
        m -= public_instance_methods( true )
      when :private
        m -= private_instance_methods( true )
      when :protected
        m -= protected_instance_methods( true )
      when :inherited, :ancestors
        m -= ( public_instance_methods( true ) - public_instance_methods( false ) )
        m -= ( private_instance_methods( true ) - private_instance_methods( false ) )
        m -= ( protected_instance_methods( true ) - protected_instance_methods( false ) )
      when :local
        m -= public_instance_methods( false )
        m -= private_instance_methods( false )
        m -= protected_instance_methods( false )
      end
    end

    m.sort
  end

# old-version
#     args << :public unless ( args.include?(:private) or args.include?(:protected) )
#     args[0] = [ :ancestors, :local ] if TrueClass === args[0]
#     args.flatten!
#     raise ArgumentError if args.any?{ |a| ! METHOD_SYMBOLS.include?(a) }
#     m = []
#     a = args.include?( :all )
#     i = ( args.include?( :inherited ) or args.include?( :ancestors ) )
#     l = ( args.include?( :local ) or args.include?( :no_ancestors ) )
#     if a
#       m |= public_instance_methods( true )
#       m |= private_instance_methods( true )
#       m |= protected_instance_methods( true )
#     elsif i and l
#       m |= public_instance_methods( true ) if args.include?( :public )
#       m |= private_instance_methods( true ) if args.include?( :private )
#       m |= protected_instance_methods( true ) if args.include?( :protected )
#     elsif i
#       m |= (public_instance_methods( true ) - public_instance_methods( false )) if args.include?( :public )
#       m |= (private_instance_methods( true ) - private_instance_methods( false )) if args.include?( :private )
#       m |= (protected_instance_methods( true ) - protected_instance_methods( false )) if args.include?( :protected )
#     else
#       m |= public_instance_methods( ! l ) if args.include?( :public )
#       m |= private_instance_methods( ! l ) if args.include?( :private )
#       m |= protected_instance_methods( ! l ) if args.include?( :protected )
#     end
#     m

end



#  _____         _
# |_   _|__  ___| |_
#   | |/ _ \/ __| __|
#   | |  __/\__ \ |_
#   |_|\___||___/\__|
#
=begin test

  require 'test/unit'

  # fixture

  class O
    def a ; end
  end

  # get a hold of the original definition _before require_
  $imF = Module.instance_method( :instance_methods )

  class TC_Methods < Test::Unit::TestCase

    def test_instance_methods
      assert_equal( $imF.bind(O).call.sort, O.instance_methods )
    end

    def test_instance_methods_true
      assert_equal( $imF.bind(O).call(true).sort, O.instance_methods(true) )
    end

    def test_public_instance_methods
      assert_equal( O.public_instance_methods.sort, O.instance_methods )
    end

    def test_private_instance_methods
      assert_equal( O.private_instance_methods.sort, O.instance_methods(:private) )
    end

    def test_protected_instance_methods
      assert_equal( O.protected_instance_methods.sort, O.instance_methods(:protected) )
    end

    def test_local_instance_methods
      assert_equal( ["a"], O.instance_methods(:local) )
    end

    def test_instance_method_local_and_ancestors
      assert_equal( $imF.bind(O).call(true).sort, O.instance_methods(:local, :ancestors) )
    end

  end

=end
