#!/usr/bin/ruby
module Kernel
  alias :puts_old :puts
  alias :p_old :p
  $outputs = []

  def puts *args
    $outputs = $outputs + args
    puts_old *args
  end

  def p *args
    $outputs = $outputs + args
    p_old *args
  end
end

class KTracePoint
  def initialize &block
    @block = block
    @binding = nil
  end

  def enable
    set_trace_func proc { |event, file, line, id, binding, classname|
      if event.to_s == 'raise'
        #puts "exception : #{$!}"
      end
      if event.to_s == 'line'
        @path = file
        @lineno = line
        @binding = binding
        @cur_caller = caller
        @block.call self
      end
    }
  end
  def disable
    set_trace_func nil
  end

  attr_reader :path
  attr_reader :lineno
  attr_reader :binding
  attr_reader :cur_caller
end

SHIFT_STACKS = 5

class RDebugger
  def initialize 
    @status = :prepared
    @last_exception = nil
    @breaks = {}
    @variables = nil
    @stacks = []
    @last_caller = nil
    
    @tp = KTracePoint.new do |tp|
      on_line tp
    end

    require 'socket'           
    @server = TCPServer.new(9090)
  
  end

  def start
    recv_msg
  end

  def stop
  end

  def send_msg(msg)
    s = TCPSocket.open('127.0.0.1', 9091)
    s.puts msg
    s.close
  end

  def recv_msg()
    $kxApp.processEvents 0
    client = @server.accept
    msg = client.gets
    client.close
    
    return if msg.nil?

    begin
      on_message msg
    rescue => e
      @last_exception = e
      @status = :raised
    end
    $kxApp.processEvents 0
  end

  def on_line tp
    @cur_match_func = @cur_match_func || Proc.new { false }
    on_break if @cur_match_func.call(tp) or match_break?(tp.path, tp.lineno)
  end

  def on_response(result = nil)
    @variables = []
    if !@tp.binding.nil? and @last_exception == nil
      @variables = @tp.binding.eval "local_variables"
    end
    if !@variables.empty?
      variables = "";
      @variables.each do |var|
        if var != nil
          val = self.peek(var);
          variables << "#{var.to_s}|#{val.inspect}|#{val.class.to_s}"
          variables << ";"
        end
      end
    end

    output = nil
    if !$outputs.empty?
      if result.nil?
        output = $outputs.to_s
      end
      $outputs.clear
    end
    response = {
      :status => @status,
      :fpath => @tp.path,
      :lineno => @tp.lineno,
      :last_exception => @last_exception.to_s,
      :variables => variables,
      :stacks => @stacks,
      :result => result.to_s,
      :outputs => output
    }.to_s

    send_msg response
    if @status != :wait_break
      recv_msg
    end
  end

  def on_break
    @cur_match_func = nil
    if @status == :breaked
      @stacks = caller[SHIFT_STACKS..-1]
      @last_caller = @tp.cur_caller
      @last_file = @tp.path
      @last_line = @tp.lineno
    end
    if @status != :terminate and @status != :raised
       @status = :breaked
    end
    on_response
  end

  def on_message(msg)
    return "params error" if msg.nil?
    command = eval msg.to_s
    return "parser error" if command.nil? || command.empty?
    self.send command[:command], command
  end

  def debug(command)
    @cur_match_func = Proc.new { |tp|
      ret = false
      if tp.path == command[:params]
        ret = true
      end
      ret
    }
    @tp.enable
    load command[:params]
  end

  def do_eval(command)
    on_response @tp.binding.eval command[:params]
  end  

  def set_break(command)
    @cur_match_func = Proc.new { true }
    file = command[:file]
    lineno = command[:lineno]
    bset = command[:bset]

    if !@breaks.include?(file)
      @breaks[file] = []
    end
    linenos = @breaks[file]
    
    if bset and !linenos.include?(lineno)
      linenos << lineno
    else
      if !bset and linenos.include?(lineno)
        linenos.delete lineno
      end
    end
    on_response
  end

  def match_break? fpath, lineno
    return false if !@breaks.include?(fpath)
    linenos = @breaks[fpath]
    return false if !linenos.include?(lineno)
    true
  end


  def clear_last_exception(command)
    @last_exception = nil
  end

  def peek var
    return [] if @status != :breaked
    @tp.binding.eval var.to_s
  end

  def step_into(command)
    @step_into_proc = @step_into_proc || Proc.new { true }
    @cur_match_func = @step_into_proc
  end

  def step_over(command)
    @step_over_proc = @step_over_proc || Proc.new { |tp|
      @last_caller == nil || tp.cur_caller.length <= @last_caller.length
    }
    @cur_match_func = @step_over_proc
  end

  def step_out(command)
    if @status == :breaked
      @step_out_proc = @step_out_proc || Proc.new { |tp|
        @last_caller == nil || tp.cur_caller.length < @last_caller.length
      }
      @cur_match_func = @step_out_proc
    else
      step_into
    end
  end

  def run(command)
    @status = :wait_break
    @cur_match_func = Proc.new { false }
  end

  def terminate(command)
    @tp.disable
    @status = :terminate
    @server.close
  end

end

#kso_plugin

#4AF8A57291635F37A0368CA41E1DFDA27418CA02A5E3F17B065CB15784D921D527AE3B6A982F74D06D12FBE9AA02C78FE67EB2B8756658239B3E154B0D270A86

#9CBE4C884719278144EC6C262906EE34FB8FD1D89E56AB37D77E816FDA102BF2EED0305AB057709DB26C67731F05E5CDCD6B9DCAEA9655343FD8DAF546DB7EAC