# taken from irb
# Implements tab completion for Readline in Pry
class Pry::InputCompleter
  NUMERIC_REGEXP            = /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/
  ARRAY_REGEXP              = /^([^\]]*\])\.([^.]*)$/
  SYMBOL_REGEXP             = /^(:[^:.]*)$/
  SYMBOL_METHOD_CALL_REGEXP = /^(:[^:.]+)\.([^.]*)$/
  REGEX_REGEXP              = /^(\/[^\/]*\/)\.([^.]*)$/
  PROC_OR_HASH_REGEXP       = /^([^\}]*\})\.([^.]*)$/
  TOPLEVEL_LOOKUP_REGEXP    = /^::([A-Z][^:\.\(]*)$/
  CONSTANT_REGEXP           = /^([A-Z][A-Za-z0-9]*)$/
  CONSTANT_OR_METHOD_REGEXP = /^([A-Z].*)::([^:.]*)$/
  HEX_REGEXP                = /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/
  GLOBALVARIABLE_REGEXP     = /^(\$[^.]*)$/
  VARIABLE_REGEXP           = /^([^."].*)\.([^.]*)$/

  ReservedWords = [
                   "BEGIN", "END",
                   "alias", "and",
                   "begin", "break",
                   "case", "class",
                   "def", "defined", "do",
                   "else", "elsif", "end", "ensure",
                   "false", "for",
                   "if", "in",
                   "module",
                   "next", "nil", "not",
                   "or",
                   "redo", "rescue", "retry", "return",
                   "self", "super",
                   "then", "true",
                   "undef", "unless", "until",
                   "when", "while",
                   "yield" ]

  Operators = [
               "%", "&", "*", "**", "+",  "-",  "/",
               "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>",
               "[]", "[]=", "^", "!", "!=", "!~"
              ]

  WORD_ESCAPE_STR = " \t\n\"\\'`><=;|&{("

  def initialize(input, pry = nil)
    @pry = pry
    @input = input
    @input.basic_word_break_characters = WORD_ESCAPE_STR if @input.respond_to?(:basic_word_break_characters=)
    @input.completion_append_character = nil if @input.respond_to?(:completion_append_character=)
  end

  #
  # Return a new completion proc for use by Readline.
  #
  def call(str, options = {})
    custom_completions = options[:custom_completions] || []
    # if there are multiple contexts e.g. cd 1/2/3
    # get new target for 1/2 and find candidates for 3
    path, input = build_path(str)

    if path.call.empty?
      target = options[:target]
    else
      # Assume the user is tab-completing the 'cd' command
      begin
        target = Pry::ObjectPath.new(path.call, @pry.binding_stack).resolve.last
      # but if that doesn't work, assume they're doing division with no spaces
      rescue Pry::CommandError
        target = options[:target]
      end
    end

    begin
      bind = target
      # Complete stdlib symbols
      case input
      when REGEX_REGEXP # Regexp
        receiver = $1
        message = Regexp.quote($2)
        candidates = Regexp.instance_methods.collect(&:to_s)
        select_message(path, receiver, message, candidates)
      when ARRAY_REGEXP # Array
        receiver = $1
        message = Regexp.quote($2)
        candidates = Array.instance_methods.collect(&:to_s)
        select_message(path, receiver, message, candidates)
      when PROC_OR_HASH_REGEXP # Proc or Hash
        receiver = $1
        message = Regexp.quote($2)
        candidates = Proc.instance_methods.collect(&:to_s)
        candidates |= Hash.instance_methods.collect(&:to_s)
        select_message(path, receiver, message, candidates)
      when SYMBOL_REGEXP # Symbol
        if Symbol.respond_to?(:all_symbols)
          sym        = Regexp.quote($1)
          candidates = Symbol.all_symbols.collect{|s| ":" << s.id2name}
          candidates.grep(/^#{sym}/)
        else
          []
        end
      when TOPLEVEL_LOOKUP_REGEXP # Absolute Constant or class methods
        receiver = $1
        candidates = Object.constants.collect(&:to_s)
        candidates.grep(/^#{receiver}/).collect{|e| "::" << e}
      when CONSTANT_REGEXP # Constant
        message = $1
        begin
          context = target.eval("self")
          context = context.class unless context.respond_to? :constants
                                    candidates = context.constants.collect(&:to_s)
                                  rescue
                                    candidates = []
                                  end
          candidates = candidates.grep(/^#{message}/).collect(&path)
        when CONSTANT_OR_METHOD_REGEXP # Constant or class methods
          receiver = $1
          message = Regexp.quote($2)
          begin
            candidates = eval("#{receiver}.constants.collect(&:to_s)", bind)
            candidates |= eval("#{receiver}.methods.collect(&:to_s)", bind)
          rescue Pry::RescuableException
            candidates = []
          end
          candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
        when SYMBOL_METHOD_CALL_REGEXP # method call on a Symbol
          receiver = $1
          message = Regexp.quote($2)
          candidates = Symbol.instance_methods.collect(&:to_s)
          select_message(path, receiver, message, candidates)
        when NUMERIC_REGEXP
          # Numeric
          receiver = $1
          message = Regexp.quote($5)
          begin
            candidates = eval(receiver, bind).methods.collect(&:to_s)
          rescue Pry::RescuableException
            candidates = []
          end
          select_message(path, receiver, message, candidates)
        when HEX_REGEXP
          # Numeric(0xFFFF)
          receiver = $1
          message = Regexp.quote($2)
          begin
            candidates = eval(receiver, bind).methods.collect(&:to_s)
          rescue Pry::RescuableException
            candidates = []
          end
          select_message(path, receiver, message, candidates)
        when GLOBALVARIABLE_REGEXP # global
          regmessage = Regexp.new(Regexp.quote($1))
          candidates = global_variables.collect(&:to_s).grep(regmessage)
        when VARIABLE_REGEXP # variable
          receiver = $1
          message = Regexp.quote($2)

          gv = eval("global_variables", bind).collect(&:to_s)
          lv = eval("local_variables", bind).collect(&:to_s)
          cv = eval("self.class.constants", bind).collect(&:to_s)

          if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
            # foo.func and foo is local var. OR
            # Foo::Bar.func
            begin
              candidates = eval("#{receiver}.methods", bind).collect(&:to_s)
            rescue Pry::RescuableException
              candidates = []
            end
          else
            # func1.func2
            candidates = []
            ObjectSpace.each_object(Module){|m|
              begin
                name = m.name.to_s
              rescue Pry::RescuableException
                name = ""
              end
              next if name != "IRB::Context" and
              /^(IRB|SLex|RubyLex|RubyToken)/ =~ name

              # jruby doesn't always provide #instance_methods() on each
              # object.
              if m.respond_to?(:instance_methods)
                candidates.concat m.instance_methods(false).collect(&:to_s)
              end
            }
            candidates.sort!
            candidates.uniq!
          end
          select_message(path, receiver, message, candidates)
        when /^\.([^.]*)$/
          # Unknown(maybe String)
          receiver = ""
          message = Regexp.quote($1)
          candidates = String.instance_methods(true).collect(&:to_s)
          select_message(path, receiver, message, candidates)
        else
          candidates = eval(
                            "methods | private_methods | local_variables | " \
                            "self.class.constants | instance_variables",
                            bind
                            ).collect(&:to_s)

          if eval("respond_to?(:class_variables)", bind)
            candidates += eval("class_variables", bind).collect(&:to_s)
          end
          candidates = (candidates|ReservedWords|custom_completions).grep(/^#{Regexp.quote(input)}/)
          candidates.collect(&path)
        end
      rescue Pry::RescuableException
        []
      end
    end

    def select_message(path, receiver, message, candidates)
      candidates.grep(/^#{message}/).collect { |e|
        case e
        when /^[a-zA-Z_]/
          path.call(receiver + "." << e)
        when /^[0-9]/
        when *Operators
          #receiver + " " << e
        end
      }.compact
    end

    # build_path seperates the input into two parts: path and input.
    # input is the partial string that should be completed
    # path is a proc that takes an input and builds a full path.
    def build_path(input)
      # check to see if the input is a regex
      return proc {|i| i.to_s }, input if input[/\/\./]
      trailing_slash = input.end_with?('/')
      contexts = input.chomp('/').split(/\//)
      input = contexts[-1]
      path = proc do |i|
        p = contexts[0..-2].push(i).join('/')
        p += '/' if trailing_slash && !i.nil?
        p
      end
      return path, input
    end
end
