require 'phi'
require 'observer'
require 'lib/rbbr-lib'
autoload(:PhiHierarchy, 'lib/phi-lib')

module Phi

  class TreeNode

    def each_node_defined_in_aprbbr
      ( 0..(self.count - 1) ).each do |num|
	yield self[num]
      end
    end

    def each_node_defined_in_aprbbr_org
      arr = []
      ( 0..(self.count - 1) ).each do |num|
        arr.push( self[num] )
      end
      arr.each do |node|
        yield node
      end
    end

  end

  class TreeNodes

    def each_node_defined_in_aprbbr
      ( 0..(self.count - 1) ).each do |num|
	yield self[num]
      end
    end

    def each_node_defined_in_aprbbr_org
      arr = []
      ( 0..(self.count - 1) ).each do |num|
        arr.push( self[num] )
      end
      arr.each do |node|
        yield node
      end
    end

  end

end


module ApRBBR

  def eval_modul_name(name)
    modul = nil
    if name == 'fatal'
      ObjectSpace.each_object(Class) do |klass|
	modul = klass if klass.name == "fatal"
      end
    else
      begin
	modul = eval(name)
      rescue
	print "BUG: eval_modul_name \n"
      end
    end
    modul
  end
  
  module_function :eval_modul_name

  class BaseTree < Phi::TreeView

    include Observable

    def initialize(form, compo_name)
      super(form, compo_name)
      self.align = Phi::AL_LEFT
      self.read_only = true
      self.width = (form.width / 3).to_i
      @showing_modul_name = ''

      # ap-rbbr.rbでadd_observerで設定されたオブジェクトに対して
      # updateを実行させる
      self.on_click = proc do
	unless selected.nil?
	  if @showing_modul_name != selected.text
	    notify_modul_to_observers(selected.data)
	  end
	end
      end

      # on_expandingのイベントハンドラの設定
      self.on_expanding = proc do |sender, allow, expanding_node|
	self.items.update do 
	  modul = expanding_node.data
	  # 下の関数は継承クラスで定義
	  append_modul_on_expanding(expanding_node, modul)
	end
      end

    end

    def notify_modul_to_observers(modul)
      @showing_modul_name = modul.name
      changed
      notify_observers(modul)
    end

    private :notify_modul_to_observers

    def update(root_klass = Object)
      self.items.update do 
	update_root(root_klass)
      end
    end

    # ノードに対するsortを使う?
    def append_sub_moduls_to_node_org(node, sub_moduls)
      if node.count == 0
	sub_moduls.sort{|x, y| x.name <=> y.name}.each do |sub_modul|
	  child_node = self.items.add_child(node, sub_modul.name)
	  child_node.data = sub_modul
	end
      end
    end

    protected :append_sub_moduls_to_node_org

    # 焼け石に水
    def append_sub_moduls_to_node(node, sub_moduls)
      if node.count == 0
	moduls = {}
	sub_moduls.each do |mod|
	  moduls[mod.name] = mod
	end
	
	moduls.keys.sort.each do |name|
	  child_node = self.items.add_child(node, name)
	  child_node.data = moduls[name]
	end
      end
    end

    protected :append_sub_moduls_to_node

  end

  class ClassTree < BaseTree

    WHOLE_TREE = 10
    APOLLO_ONLY = 11

    def initialize(form, compo_name, contents = WHOLE_TREE)
      super(form, compo_name)
      set_contents(contents)
      update_root
    end

    attr_reader :contents

    def set_contents(value)
      case value
      when WHOLE_TREE
	@contents = WHOLE_TREE
      when APOLLO_ONLY
	@contents = APOLLO_ONLY
      end
    end

    # Apollo_ONLY と WHOLE_TREE の場合で返す値が違う
    def get_sub_klasses(parent_klass)
      if @contents == APOLLO_ONLY
	if @hierarchy.klass_relation.key?(parent_klass)
	  child_klasses = @hierarchy.klass_relation[parent_klass]
	else
	  # この段階でparent_klassにはApolloのクラスを親に持つクラスしかこない
	  child_klasses = parent_klass.sub_classes
	end
      else
	child_klasses = parent_klass.sub_classes
      end
      child_klasses
    end

    private :get_sub_klasses

    # 初期化用の関数。ツリーの初期化とルートノードの付加
    def update_root(root_klass = Object)
      if @contents == APOLLO_ONLY
	@hierarchy = PhiHierarchy.new
      end
      nodes = self.items
      nodes.clear
      root_node = nodes.add(nil, root_klass.name)
      root_node.data = root_klass

      # rootノードのすぐ下のクラスは付加しておく
      sub_klasses = get_sub_klasses(root_klass)
      append_sub_moduls_to_node(root_node, sub_klasses)
    end

    private :update_root

    # on_expandingの時に呼ばれる関数
    def append_modul_on_expanding(expanding_node, selected_klass)
      append_two_level_lower_classes(expanding_node, selected_klass)
    end

    private :append_modul_on_expanding

    # parent_nodeの一つ下にノードがすでについている状態で
    # parent_nodeの二つ下のノードをつける
    def append_two_level_lower_classes(parent_node, parent_klass)
      parent_node.each_node_defined_in_aprbbr do |child_node|
	child_klass = child_node.data

	grandchild_klasses = get_sub_klasses(child_klass)
	if grandchild_klasses.empty?
	  next
	else
	  append_sub_moduls_to_node(child_node, grandchild_klasses)
	end
      end
    end

    private :append_two_level_lower_classes

    # wrapper
    def search_class(class_name)
      # 存在するか確認
      if @contents == WHOLE_TREE
  	result = exist_class_name?(class_name)
      elsif @contents == APOLLO_ONLY
  	result = exist_apollo_class_name?(class_name)
      end

      if result
	if $DEBUG 
	  p class_name
	end
	klass = ApRBBR::eval_modul_name(class_name)
	expand_class_node(klass)
	notify_modul_to_observers(klass)
      end
      result
    end

    # ちょっとダサい。
    def expand_class_node(klass)
      ancestor_klasses = get_super_klasses(klass)
      wanted_class_name = klass.name
      nodes = self.items
      expand_node = nodes[0]

      nodes.update do 
	ancestor_klasses.reverse_each do |ancestor_klass|
	  ancestor_class_name = ancestor_klass.name
	  expand_node.expand(false)

	  if $DEBUG
	    print "expand_node: #{ancestor_class_name} "
	    p expand_node.text
	    print "\n"
	  end
	  
	  # 探しているのは wanted_class_name のtextを持つノードなので、
	  # wanted_class_name のtextを持つノードを開く必要はない
	  if ancestor_class_name == wanted_class_name
	    if $DEBUG
	      p "hit"
	      p expand_node.text
	    end
	    
	    # 検索対象であるklassのノードの親のノードがexpand_nodeに入っているので、
	    # klassのノードを親ノードから見つける
	    wanted_node = nil
	    expand_node.each_node_defined_in_aprbbr do |node|
	      if node.text == wanted_class_name
		wanted_node = node
		break
	      end
	    end
	    
	    # 後はスクロールだけ
	    self.selected = wanted_node
	    self.set_focus
	    break
	  end
	    
	  # nodeをexpandして、その子ノードの中の
	  # ancestor_class_nameというtextを持つ子ノードを探し、
	  # それを次にexpandするノードとする
	  expand_node.each_node_defined_in_aprbbr do |node|
	    if node.text == ancestor_class_name
	      expand_node = node
	      break
	    end
	  end

	end
      end
      
      return true
    end

    private :expand_class_node

    def exist_class_name?(class_name)
      ObjectSpace.each_object(Class) do |klass|
	if klass.name == class_name
	  return true
	end
      end

      return false
    end

    private :exist_class_name?

    def exist_apollo_class_name?(class_name)
      ObjectSpace.each_object(Class) do |ap_klass|
	ap_class_name = ap_klass.name
	# Apolloのクラスかどうか判別
	if ap_class_name == class_name and @hierarchy.apollo_modul_regexp =~ ap_class_name
	  return true
	end
      end

      return false
    end

    private :exist_apollo_class_name?

    # Moduleのクラス関数にでもすべき
    def get_super_klasses(klass)
      klass.ancestors - klass.included_modules
    end

    private :get_super_klasses

  end
    

  class ModuleTree < BaseTree

    # ノードの付加はオブジェクトの生成時には行わない。
    # 起動を速くするためにモジュールのタブが初めてアクティブに
    # なったときに付加する。
    def update_root(psuedo_arg = nil)
      self.items.clear
      append_root_modules
    end

    private :update_root

    # rbbr.rbのアルゴリズムを変更
    # こちらの方が速い理由がわからない
    private
    def append_root_modules
      root_mods = {}
      Module.root_modules.each do |mod|
	root_mods[mod.name] = mod
      end
      
      root_mods.keys.sort!.each do |root_mod_name|
	# ''な名前のモジュールに対する場当たり的対処(ぉ
	if root_mod_name == ''
	  next
	end
	
	root_node = self.items.add(nil, root_mod_name)
        root_module = root_mods[root_mod_name]
	root_node.data = root_module
        child_modules = root_module.sub_modules

	if child_modules.empty?
	  next
	else
	  append_sub_moduls_to_node(root_node, child_modules)
	end
      end

    end

    private :append_root_modules

    # on_expandingの時に呼ばれる関数
    def append_modul_on_expanding(expanding_node, selected_module)
      append_two_level_lower_modules(expanding_node, selected_module)
    end

    private :append_modul_on_expanding

    # parent_nodeの一つ下にノードがすでについている状態で
    # parent_nodeの二つ下のノードをつける
    private
    def append_two_level_lower_modules(parent_node, parent_module)
      parent_node.each_node_defined_in_aprbbr do |child_node|
	child_module = child_node.data
	grandchild_modules = child_module.sub_modules

	if grandchild_modules.empty?
	  next
	else
	  append_sub_moduls_to_node(child_node, grandchild_modules)
	end
      end
    end

    private :append_two_level_lower_modules

  end

end


if $0 == __FILE__ 
  form = Phi::Form.new
  form.width = 400
  form.height = 400
  require '../bench'
  tree = nil

  #benchmark('tree', 1){
  #tree = ApRBBR::ClassTree.new(form, :claas_tree1)
  #}
  tree = ApRBBR::ModuleTree.new(form, :claas_tree1)

  tree.align = Phi::AL_CLIENT

  btn = Phi::Button.new(form, :btn1)
  btn.align = Phi::AL_BOTTOM
  btn.height = 50

  btn.on_click = proc do 
    tree.update 
  end

  form.show
  Phi.mainloop
end
