# -*- coding: binary -*-

module Rex
module Ui
module Text

begin

  ###
  #
  # This class implements standard input using readline against
  # standard input.  It supports tab completion.
  #
  ###
  class Input::Readline < Rex::Ui::Text::Input

    #
    # Initializes the readline-aware Input instance for text.
    #
    def initialize(tab_complete_proc = nil)
      if(not Object.const_defined?('Readline'))
        require 'readline'
      end

      self.extend(::Readline)

      if tab_complete_proc
        ::Readline.basic_word_break_characters = ""
        @rl_saved_proc = with_error_handling(tab_complete_proc)
        ::Readline.completion_proc = @rl_saved_proc
      end
    end

    #
    # Reattach the original completion proc
    #
    def reset_tab_completion(tab_complete_proc = nil)
      ::Readline.basic_word_break_characters = "\x00"
      ::Readline.completion_proc = tab_complete_proc ? with_error_handling(tab_complete_proc) : @rl_saved_proc
    end


    #
    # Retrieve the line buffer
    #
    def line_buffer
      if defined? RbReadline
        RbReadline.rl_line_buffer
      else
        ::Readline.line_buffer
      end
    end

    attr_accessor :prompt

    #
    # Whether or not the input medium supports readline.
    #
    def supports_readline
      true
    end

    #
    # Calls sysread on the standard input handle.
    #
    def sysread(len = 1)
      begin
        self.fd.sysread(len)
      rescue ::Errno::EINTR
        retry
      end
    end

    #
    # Read a line from stdin
    #
    def gets()
      begin
        self.fd.gets()
      rescue ::Errno::EINTR
        retry
      end
    end

    #
    # Stick readline into a low-priority thread so that the scheduler doesn't slow
    # down other background threads. This is important when there are many active
    # background jobs, such as when the user is running Karmetasploit
    #
    def pgets

      line = nil
      orig = Thread.current.priority

      begin
        Thread.current.priority = -20

        output.prompting
        line = readline_with_output(prompt, true)
        ::Readline::HISTORY.pop if (line and line.empty?)
      ensure
        Thread.current.priority = orig || 0
      end

      line
    end

    #
    # Returns the output pipe handle
    #
    def fd
      $stdin
    end

    #
    # Indicates that this input medium as a shell builtin, no need
    # to extend.
    #
    def intrinsic_shell?
      true
    end

    #
    # The prompt that is to be displayed.
    #
    attr_accessor :prompt
    #
    # The output handle to use when displaying the prompt.
    #
    attr_accessor :output

    private

    def readline_with_output(prompt, add_history=false)
      # rb-readlines's Readline.readline hardcodes the input and output to
      # $stdin and $stdout, which means setting `Readline.input` or
      # `Readline.ouput` has no effect when running `Readline.readline` with
      # rb-readline, so need to reimplement
      # []`Readline.readline`](https://github.com/luislavena/rb-readline/blob/ce4908dae45dbcae90a6e42e3710b8c3a1f2cd64/lib/readline.rb#L36-L58)
      # for rb-readline to support setting input and output.  Output needs to
      # be set so that colorization works for the prompt on Windows.
      self.prompt = prompt

      # TODO: there are unhandled quirks in async output buffering that
      # we have not solved yet, for instance when loading meterpreter
      # extensions, supporting Windows, printing output from commands, etc.
      # Remove this guard when issues are resolved.
=begin
      reset_sequence = "\n\001\r\033[K\002"
      if (/mingw/ =~ RUBY_PLATFORM)
        reset_sequence = ""
      end
=end
      reset_sequence = ""

      if defined? RbReadline
        RbReadline.rl_instream = fd
        RbReadline.rl_outstream = output

        begin
          line = RbReadline.readline(reset_sequence + prompt)
        rescue ::Exception => exception
          RbReadline.rl_cleanup_after_signal()
          RbReadline.rl_deprep_terminal()

          raise exception
        end

        if add_history && line && !line.start_with?(' ')
          # Don't add duplicate lines to history
          if ::Readline::HISTORY.empty? || line.strip != ::Readline::HISTORY[-1]
            RbReadline.add_history(line.strip)
          end
        end

        line.try(:dup)
      else
        # The line that's read is immediately added to history
        line = ::Readline.readline(reset_sequence + prompt, true)

        # Don't add duplicate lines to history
        if ::Readline::HISTORY.length > 1 && line == ::Readline::HISTORY[-2]
          ::Readline::HISTORY.pop
        end

        line
      end
    end

    private

    def with_error_handling(proc)
      proc do |*args|
        proc.call(*args)
      rescue StandardError => e
        elog("tab_complete_proc has failed with args #{args}", error: e)
        []
      end
    end

  end
rescue LoadError
end

end
end
end
