require 'phi'
require 'rbeditor'
require 'string_class'
require 'completion'

autoload(:LS, 'ls-lib')

def p2(*args)
  STDOUT.p(*args) 
end

module Phi

  # IRBからの入力は"\n"を含む場合もあれば、
  # 含まない場合もあり、、
  # Phi::Strings#addとは相性が悪いです。
  # そのためGtk::Text#insertのような動作をする
  # Phi::Strings#add_textを作りました。
  # ただし、この実装はIRB専用なので、
  # ad-hocな感じは否めません。

  # まずIRBでは出力を最後に付加するだけでよいので、
  # Phi::Strings#add_textでは最後の行への付加しか考えていません。
  
  # Phi::Strings#add_textでは"\n"に対する動作を自前で
  # 実装するためにIRBからの入力に"\n"があるか調べます。
  # "\n"があるときは最後の行に文字列を追加して、
  # Phi::Strings#addに''(空の文字列)を渡して改行させます。
  # (文字列が何も無いときは違いますが)
  # "\n"が無いときは最後の行に文字列を追加します。

  # ユーザーからの入力はIRBEditorの方で処理します。
  class Strings 

    def last_line
      if self.count == 0
	''
      else
	self[ self.count - 1 ]
      end
    end

    
    # 最後の文字が改行かそうでないかを
    # 判断して文字列の追加方法を変える
    def add_text(string)
      # 配列に文字がない時
      if self.count == 0
	if string.last_char_is_linefeed?
	  self.add(string.chomp)
	else
	  self.add(string)
	end
	return true
      end

      # 配列に文字があるとき
      if string.last_char_is_linefeed?
	self[self.count - 1] = 	last_line.concat(string.chomp)
	self.add('')
      else
	self[self.count - 1] = 	last_line.concat(string)
      end
    end


  end
end


module ApIRB

  # 最新の履歴は@history_arrの先頭にある
  class IRBHistory
    
    def initialize(strings)
      @history_arr = []
      @current_num = 0
      @max = 100
      @strings = strings
    end
    
    attr_reader :current_num

    def init_current
      @current_num = 0
    end

    def add_history(str)
      # 最後を消す
      if @history_arr.size > @max
	@history_arr.pop
      end
      
      case str
      when  /^\s*$/
	# 空白や空行は無視
      else
	# 先頭に追加
	@history_arr.unshift(str)
      end
    end
    
    def insert_history_str(string, last_point)
      @strings[@strings.count - 1] = @strings.last_line[0..(last_point - 1)].concat(string)
    end
    
    # 最後に挿入することを前提にしている
    def insert_previous_history(point)
      # 前の履歴はない
      if (@history_arr.size - 1 < @current_num) 
	#beep 
	return false
      end
      
      @current_num
      str = @history_arr[@current_num]
      @current_num += 1
      insert_history_str(str, point) 
    end
    
    # 場当たり的(ぉ
    def insert_next_history(point)
      @current_num -= 1
      if @current_num < 0
	@current_num = 0
	return false
      elsif @current_num == 0 
	# 一度も insert_previous_history を実行してない状態で 
	# insert_next_historyをすると、空白になる。
	insert_history_str('', point)
	return false
      end

      # 現在の履歴番号を減らして、履歴を得る
      str = @history_arr[@current_num - 1]
      insert_history_str(str, point)
    end

    def get_history
      @history_arr
    end
    
    alias previous insert_previous_history
    alias next insert_next_history
  end


  # IRBEditor#buffer_getsが最終的にIRBへの出力を
  # 決めます。IRBEditor#buffer_getsの実装はGTKと同じく
  # ユーザーからReturnの入力があるまで
  # ループに入り、その間再描画の命令を実行させます。
  # ユーザーからReturnが入力されると
  # 最後にIRBEditor#buffer_writeで入力された文字列の
  # 最後からその行末までをユーザーからの入力とみなして
  # IRBEditor#buffer_getsの出力とします。
  class IRBEditor < RBEditor
    
    def initialize(form, com_name)
      super(form, com_name)
      @can_gets = false
      @phi_lines = self.lines
      @phi_lines.clear
      @last_char_point_of_buffer_write = 0
      @history = ApIRB::IRBHistory.new(@phi_lines)
      @completion = ApIRB::IRBCompletion.new(self)

      #
      # イベントハンドラ
      #
      
      # Returnを押した時の改行の処理が無視される?
      # 無視されて欲しいんだけど
      self.on_key_press = proc do |editor, key_code|
	if key_code == Phi::VK_RETURN
	  @phi_lines.add("")
	  @history.add_history(get_input_string_after_linefeed_is_inputed)
	  @history.init_current
	  @can_gets = true
	end
      end
      
      # EOFに移動したら強制的に元に戻す
      # buffer_writeの後もEOFに動くので、その時も一行上に動かす
      self.on_caret_moved = proc do
	#p2 self.col, self.row
	#p2 k = self.list_count

	if (self.col == 0) and (self.row == self.list_count)
	  # 最後の行の末尾の位置を取得
	  if @phi_lines.last_line.get_str_byte == 0
	    col_move_point = 0
	  else
	    col_move_point = @phi_lines.last_line.get_str_byte - 1
	  end

	  # 最後行の行数
	  row_move_point = self.row - 1
	  
	  if row_move_point >= 0 
	    self.set_row_col(row_move_point, col_move_point)
	  end
	end
      end

      self.on_resize = proc do 
	#col_countを使うべき
	self.wrap_option.wrap_byte = (self.width / 9).to_i
      end

    end
    
    attr_reader :history, :last_char_point_of_buffer_write, :completion
    
    def buffer_write(str)
      begin
	@phi_lines.add_text(str)
	@last_char_point_of_buffer_write = @phi_lines.last_line.get_str_byte
	true
      rescue
	false
      end
    end
    
    alias buffer_putc buffer_write
    
    def buffer_gets(rs = $/)
      while true
	# Retun が入力されるまでループ
	if @can_gets
	  break
	end
	
	# 再描画
	Phi::APPLICATION.process_messages
	sleep 0.01
      end

      str = get_input_string_after_linefeed_is_inputed
      @can_gets = false
      "#{str}\n"
    end

    def get_prompt_str
      last_line = @phi_lines.last_line
      if last_line.size > 0 
	last_line[0..(@last_char_point_of_buffer_write - 1)]
      else
	''
      end
    end

    # ユーザーによって入力された最初の文字は
    # buffer_writeで書き出された最後の文字のすぐ後にあるものとする。
    def get_input_string(input_line_num)
      if input_line_num < 0
	input_line = ''
      else
	input_line = @phi_lines[input_line_num]
      end
      input_line[@last_char_point_of_buffer_write..-1]
    end

    # ユーザーがリターンを押す前の段階でのユーザーの入力を得る
    def get_input_string_before_linefeed_is_inputed
      get_input_string(@phi_lines.count - 1)
    end

    # リターンを押した後、空行が入力されるので、
    # 入力行は最後から2行目になる。

    # すでに入力されている文字列を編集すると
    # buffer_getの出力はおかしくなるが、
    # そこまで面倒見れない(ぉ
    # 書きこみ禁止 or 挿入した文字列を逐一削除 する?
    def get_input_string_after_linefeed_is_inputed
      get_input_string(@phi_lines.count - 2)
    end

    def complete
      org_col = self.col
      input = get_input_string_before_linefeed_is_inputed
      last_line = @phi_lines.last_line
      col_point = org_col - last_char_point_of_buffer_write
      input_word = @completion.get_input_word(input, col_point)
      list = @completion.get_completion_candidates(input_word)

      case list.size 
      when 0
      when 1
	# そのままだと?とかでおかしくなる
	completion_str = list[0].sub(/^#{Regexp.quote(input_word)}/, '')

	@phi_lines[@phi_lines.count - 1] = last_line[0..(org_col-1)].concat(completion_str)
	if org_col < last_line.get_str_byte - 1
	  @phi_lines[@phi_lines.count - 1] = @phi_lines[@phi_lines.count - 1].concat(last_line[(org_col)..-1])
	end
	self.set_row_col(@phi_lines.count - 1, org_col + completion_str.get_str_byte)
      else
	ls = LS.new(list.sort!, self.wrap_option.wrap_byte)
	@phi_lines.add(ls.get_result_string)
	@phi_lines.add_text(last_line)
	self.set_row_col(@phi_lines.count - 1, org_col)
      end
    end

  end
   
end






if $0 == __FILE__
  require 'rgui/ui'
# require 'inspect-my'
  
  w = RGUI::Form.new(:form1)
  w.height = 350
  w.width = 400
  
  btns = [
    btn1 = Phi::Button.new(w, :btn1_1),
    btn2 = Phi::Button.new(w, :btn2_1),
    btn3 = Phi::Button.new(w, :btn3_1),
  ]
  hbox = UI::Hbox.new([btns], 5)
  hbox.height = 35
  
  editor = ApIRB::IRBEditor.new(w, :editor1)
  vbox = UI::Vbox.new(editor, 5)
  vbox.height = w.width - hbox.height
  
  top_vbox = UI::Vbox.new([vbox, hbox])
  w.add(top_vbox)
  w.layout
  w.show
  
  btn1.on_click = proc do 
    buf = editor.lines
    p editor.buffer_gets
    #p buf.last_line
    #buf.methods.sort.each do |l|
    #  buf.add(l)
    #end
  end
  
  btn2.on_click = proc do 
    buf = editor.lines
    p buf.count
    if buf.count > 0
      print "last line: "
      p buf.last_line
      print "last char: "
      p buf.last_line.last_char
    end
  end
  
  btn3.on_click = proc do 
    buf = editor.lines
    case rand(3)
    when 0
      p 0
      editor.buffer_write("hogehoge\n")
      editor.buffer_write("mmmmm")
      editor.buffer_write("aaaaa\n")
    when 1
      p 1
      editor.buffer_write("きゅう")
      editor.buffer_write("さんよう")
      editor.buffer_write("うむ")
    else
      p 2
      editor.buffer_write("1234\n")
      editor.buffer_write("2345\n")
      editor.buffer_write("3059\n")
    end
  end
  
  w.on_resize = proc { w.layout }
  Phi.mainloop
end
