#!/usr/bin/env ruby


require 'bundler'
Bundler.require

require 'reline'
require 'optparse'
require_relative 'termination_checker'

opt = OptionParser.new
opt.on('--dynamic-prompt') {
  Reline.prompt_proc = proc { |lines|
    lines.each_with_index.map { |l, i|
      "\e[1m[%04d]>\e[m " % i
    }
  }
}
opt.on('--broken-dynamic-prompt') {
  Reline.prompt_proc = proc { |lines|
    range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0)
    lines[range].each_with_index.map { |l, i|
      '[%04d]> ' % i
    }
  }
}
opt.on('--dynamic-prompt-returns-empty') {
  Reline.prompt_proc = proc { |l| [] }
}
opt.on('--dynamic-prompt-with-newline') {
  Reline.prompt_proc = proc { |lines|
    range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0)
    lines[range].each_with_index.map { |l, i|
      '[%04d\n]> ' % i
    }
  }
}
opt.on('--broken-dynamic-prompt-assert-no-escape-sequence') {
  Reline.prompt_proc = proc { |lines|
    has_escape_sequence = lines.join.include?("\e")
    (lines.size + 1).times.map { |i|
      has_escape_sequence ? 'error>' : '[%04d]> ' % i
    }
  }
}
opt.on('--color-bold') {
  Reline.output_modifier_proc = ->(output, complete:){
    output.gsub(/./) { |c| "\e[1m#{c}\e[0m" }
  }
}
opt.on('--dynamic-prompt-show-line') {
  Reline.prompt_proc = proc { |lines|
    lines.map { |l|
      '[%4.4s]> ' % l
    }
  }
}

def assert_auto_indent_params(lines, line_index, byte_pointer, is_newline)
  raise 'Wrong lines type' unless lines.all?(String)

  line = lines[line_index]
  raise 'Wrong line_index value' unless line

  # The condition `byte_pointer <= line.bytesize` is not satisfied. Maybe bug.
  # Instead, loose constraint `byte_pointer <= line.bytesize + 1` seems to be satisfied when is_newline is false.
  return if is_newline

  raise 'byte_pointer out of bounds' unless byte_pointer <= line.bytesize + 1
  raise 'Invalid byte_pointer' unless line.byteslice(0, byte_pointer).valid_encoding?
end

opt.on('--auto-indent') {
  Reline.auto_indent_proc = lambda do |lines, line_index, byte_pointer, is_newline|
    assert_auto_indent_params(lines, line_index, byte_pointer, is_newline)
    AutoIndent.calculate_indent(lines, line_index, byte_pointer, is_newline)
  end
}
opt.on('--dialog VAL') { |v|
  Reline.add_dialog_proc(:simple_dialog, lambda {
    return nil if v.include?('nil')
    if v.include?('simple')
      contents = <<~RUBY.split("\n")
        Ruby is...
        A dynamic, open source programming
        language with a focus on simplicity
        and productivity. It has an elegant
        syntax that is natural to read and
        easy to write.
      RUBY
    elsif v.include?('long')
      contents = <<~RUBY.split("\n")
        Ruby is...
        A dynamic, open
        source programming
        language with a
        focus on simplicity
        and productivity.
        It has an elegant
        syntax that is
        natural to read
        and easy to write.
      RUBY
    elsif v.include?('fullwidth')
      contents = <<~RUBY.split("\n")
        Rubyとは...

        オープンソースの動的なプログラミン
        グ言語で、シンプルさと高い生産性を
        備えています。エレガントな文法を持
        ち、自然に読み書きができます。
      RUBY
    end
    if v.include?('scrollkey')
      dialog.trap_key = nil
      if key and key.match?(dialog.name)
        if dialog.pointer.nil?
          dialog.pointer = 0
        elsif dialog.pointer >= (contents.size - 1)
          dialog.pointer = 0
        else
          dialog.pointer += 1
        end
      end
      dialog.trap_key = [?j.ord]
      height = 4
    end
    scrollbar = false
    if v.include?('scrollbar')
      scrollbar = true
    end
    if v.include?('alt-scrollbar')
      scrollbar = true
    end
    Reline::DialogRenderInfo.new(pos: cursor_pos, contents: contents, height: height, scrollbar: scrollbar, face: :completion_dialog)
  })
  if v.include?('alt-scrollbar')
    ENV['RELINE_ALT_SCROLLBAR'] = '1'
  end
}
opt.on('--complete') {
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    %w{String ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
  }
}
opt.on('--complete-menu-with-perfect-match') {
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    %w{abs abs2}.select{ |c| c.start_with?(target) }
  }
}
opt.on('--autocomplete') {
  Reline.autocompletion = true
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    %w{String Struct Symbol ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
  }
}
opt.on('--autocomplete-empty') {
  Reline.autocompletion = true
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| [] }
}
opt.on('--autocomplete-long') {
  Reline.autocompletion = true
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    %w{
      String
      Struct
      Symbol
      StopIteration
      SystemCallError
      SystemExit
      SystemStackError
      ScriptError
      SyntaxError
      Signal
      SizedQueue
      Set
      SecureRandom
      Socket
      StringIO
      StringScanner
      Shellwords
      Syslog
      Singleton
      SDBM
    }.select{ |c| c.start_with?(target) }
  }
}
opt.on('--autocomplete-super-long') {
  Reline.autocompletion = true
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    c = +'A'
    2000.times.map{ s = "Str_#{c}"; c.succ!; s }.select{ |c| c.start_with?(target) }
  }
}

opt.on('--autocomplete-width-long') {
  Reline.autocompletion = true
  Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
    %w{
        remove_instance_variable
        respond_to?
        ruby2_keywords
        rand
        readline
        readlines
        require
        require_relative
        raise
        respond_to_missing?
        redo
        rescue
        retry
        return
    }.select{ |c| c.start_with?(target) }
  }
}
opt.parse!(ARGV)

begin
  stty_save = `stty -g`.chomp
rescue
end

begin
  prompt = ENV['RELINE_TEST_PROMPT'] || "\e[1mprompt>\e[m "
  puts 'Multiline REPL.'
  while code = Reline.readmultiline(prompt, true) { |code| TerminationChecker.terminated?(code) }
    case code.chomp
    when 'exit', 'quit', 'q'
      exit 0
    when ''
      # NOOP
    else
      begin
        result = eval(code)
        puts "=> #{result.inspect}"
      rescue ScriptError, StandardError => e
        puts "Traceback (most recent call last):"
        e.backtrace.reverse_each do |f|
          puts "        #{f}"
        end
        puts e.message
      end
    end
  end
rescue Interrupt
  puts '^C'
  `stty #{stty_save}` if stty_save
  exit 0
ensure
  `stty #{stty_save}` if stty_save
end
begin
  puts
rescue Errno::EIO
  # Maybe the I/O has been closed.
end
