#! ruby -Ks
# ap-list:2923
# 桜井 利彦 <t_sakurai@mac.com>
# ObjectSpace の GUI 化サンプル

require 'phi'
include Phi

#require "SimpleGetText"
#SimpleGetText.local = "en"

class WatchWindow
  def initialize
    current_list = 0

    main_form = Form.new
    # main_form.caption = "Watch Window"._
    main_form.caption = "Watch Window"
    main_form.top = 300
    main_form.left = 0
    main_form.width = 400
    main_form.height = 400

    splitter2 = Splitter.new main_form
    splitter2.align = AL_LEFT

    pnl = Panel.new main_form
    pnl.align = AL_LEFT
    pnl.width = 200

    pnl2 = Panel.new main_form
    pnl2.align = AL_CLIENT

    inst_list = ListBox.new pnl2
    inst_list.align = AL_TOP

    splitter1 = Splitter.new pnl2
    splitter1.top = 10
    splitter1.align = AL_TOP

    status_memo = Memo.new(pnl2)
    status_memo.align = AL_CLIENT
    status_memo.scroll_bars = SS_BOTH
    status_memo.height = 100

    show_inst = proc do |tmp_id|
      begin
        obj = ObjectSpace._id2ref(tmp_id.to_i)
        tmp_str = "#{obj.inspect} (#{tmp_id})"
        obj.instance_variables.sort.each {|varname|
          begin
            eval_str = obj.instance_eval(varname).to_s
          rescue Exception
            print_er "cannot evaluate #{varname}"
            raise
          end
          begin
            tmp_str = "#{tmp_str}\r\n#{varname} = " + Uconv::u8tosjis("#{eval_str}")
          rescue Exception
            tmp_str = "#{tmp_str}\r\n#{varname} = #{eval_str}"
          end
        }
        status_memo.text = tmp_str
      rescue Exception
        print_er $!.message + "\n" + $!.backtrace.join("\n")
      end
    end

    inst_list.on_click = proc do
      current_list = 1
      tmp_id = inst_list.items[inst_list.item_index]
      show_inst.call(tmp_id)
    end

    cls_list = ListBox.new pnl
    cls_list.height = 200
    cls_list.align = AL_TOP
    cls_list.on_click = proc do
      current_list = 0
      tmp_str = cls_list.items[cls_list.item_index]
      clsname = ''
      if tmp_str =~ /(\S+) : .+/
        clsname = $1
      end
      inst_list.items.clear
      cls_hash = Hash.new
      ObjectSpace.each_object(Class) {|x|
        cls_hash[x.to_s] = x
      }
      ObjectSpace.each_object(cls_hash[clsname]) {|x|
        begin
          inst_list.items.add x.id.to_s
        rescue Exception
        end
      } if clsname != ''
    end

    @prev_clss = nil
    @prev_count = 0
    update_list = proc do
      current_list = 0
      clss = Hash.new
      cls_list.items.clear
      count = 0
      ObjectSpace.each_object {|x|
        count += 1
        begin
          if clss[x.class.to_s].nil?
            clss[x.class.to_s] = 1
          else
            clss[x.class.to_s] += 1
          end
        rescue Exception
        end
      }
      delta = count - @prev_count
      @prev_count = count
      delta = (delta < 0 ? delta.to_s : "+#{delta.to_s}")
      status_memo.text = "instance count = #{count.to_s} #{(delta)}"
      clss.keys.sort.each {|key|
        delta = clss[key]
        if !@prev_clss.nil?
          c = @prev_clss[key]
          delta = c - clss[key] if !c.nil?
        end
        delta = (delta < 0 ? delta.to_s : "+#{delta.to_s}")
        cls_list.items.add "#{key} : #{clss[key].to_s} #{delta}"
      }
      @prev_clss = clss
    end

    splitter3 = Splitter.new pnl
    splitter3.top = 10
    splitter3.align = AL_TOP

    sc_button = Button.new(pnl)
    sc_button.align = AL_TOP
    sc_button.caption = "Update"
    # sc_button.caption = "Update"._
    sc_button.on_click = update_list

    del_button = Button.new(pnl)
    del_button.align = AL_TOP
    del_button.caption = "GC"
    del_button.on_click = proc do
      current_list = 0
      GC.start
      count = 0
      ObjectSpace.each_object {|x|
        count += 1
      }
      status_memo.text = "GC done\r\ninstance count = #{count.to_s}\r\n"
    end

    gc_button = Button.new(pnl)
    gc_button.align = AL_TOP
    gc_button.caption = "Show VCL"
    # gc_button.caption = "Show VCL"._
    gc_button.on_click = proc do
      current_list = 0
      status_memo.text = instance_to_text(Component)
    end

    fr_button = Button.new(pnl)
    fr_button.align = Phi::AL_TOP
    fr_button.caption = "Find Referer"
    
    referer_list = ListBox.new pnl
    referer_list.align = Phi::AL_CLIENT
    referer_list.on_click = proc do
      current_list = 2
      tmp_id = referer_list.items[referer_list.item_index]
      show_inst.call(tmp_id)
    end
    
    fr_button.on_click = proc do
      tgt_id = ''
      if current_list == 1
        tgt_id = inst_list.items[inst_list.item_index] if inst_list.item_index >= 0
      elsif current_list == 2
        tgt_id = referer_list.items[referer_list.item_index] if referer_list.item_index >= 0
      end
      referer_list.items.clear
      if tgt_id =~ /^\d+$/
        ObjectSpace.each_object {|x|
          begin
            x.instance_variables.sort.each {|varname|
              begin
                var = x.instance_eval(varname)
              rescue Exception
                print_er "cannot evaluate #{varname}"
              end
              if var.id.to_s == tgt_id
                begin
                  referer_list.items.add x.id.to_s
                rescue Exception
                end
              end
            }
          rescue Exception
            print_er $!.message + "\n" + $!.backtrace.join("\n")
          end
          
        }
      end
    end
    
    main_form.on_close = proc do
      @prev_clss.clear
      @prev_clss = nil
      @prev_count = nil
      inst_list.on_click = nil
      cls_list.on_click = nil
      sc_button.on_click = nil
      del_button.on_click = nil
      gc_button.on_click = nil
      main_form.on_close = nil
    end

    main_form.show
    update_list.call
  end

  def instance_to_text(klass)
    count = 0
    objs = Array.new
    ObjectSpace.each_object(klass) {|x|
      count += 1
      str = "#{x.class.to_s} #{x.to_s}"
      if x.respond_to?('name')
        str += " " + x.name.to_s
      end
      objs.push str
    }
    return "instance count = #{objs.size}\r\n" + objs.sort.join("\r\n")
  end
end

ww = WatchWindow.new

mainloop

