#!/usr/bin/env ruby
# -*- ruby -*-

# Copyright 2000, 2001 by Jim Weirich (jweirich@one.net).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.

require 'gem/utils'
require 'gem/whenmissing'
require 'observer'
require 'tk'

######################################################################
# Namespace for GemFinder classes.
#
module Gem

  ####################################################################
  # Model for supporting lists of classes.
  #
  class ListModel
    
    include Observable
    include GemUtils
    
    attr_reader :source, :items, :selection_index
    attr_accessor :selection_monitor
    
    def initialize
      @source = ""
      @items = []
      @selection_index = -1
      @selection_monitor = nil
    end
    
    def fill_from (klass)
      @source = klass.to_s
      update_items
      @selection_index = -1
      notify
    end
    
    def select (index)
      @selection_index = index
      do_select(index)
    end
    
    def use_class_model (cm)
      @class_model = cm
      @class_model.add_observer(self)
    end
    
    def update
      if @class_model.selection && @class_model.selection != "" then
	fill_from (@class_model.selection)
      end
    end
    
    def notify
      changed
      notify_observers
    end
    
    private
    
    def do_select (index)
      @selection_monitor.select_name(@items[index].to_s) if @selection_monitor
    end
  end
  
  ####################################################################
  class ClassListModel < ListModel
    
    attr_reader :selection
    attr_accessor :include_system
    
    # Regular expression matching classes to omit from class lists
    Class_reject_filter = /^(Errno::.*|fatal)$/
    
    def initialize
      super
      @include_system = true
      @selection = ""
    end
    
    def select_name (name)
      index = @items.index(name).when_missing {
	@items.index(name + " (M)")
      }
      select(index) if index
    end
    
    def include_system= (value)
      @include_system = value
      fill_from ("")
      notify
    end
    
    private # --------------------------------------------------------
    
    def do_select (index)
      return unless (Integer === index)
      @selection = @items[index].dup
      @selection.sub!(/ \(M\)/, "")
      notify
    end
    
    def update_items
      if @include_system then
	@items = classes
      else
	@items = remove_system_classes(classes)
      end
      @items.sort!
    end
    
    def classes
      items = []
      ObjectSpace.each_object(Module) { |n|
	if n.to_s !~ Class_reject_filter then
	  items << (n.to_s + ((Class === n) ? "" : " (M)"))
	end
      }
      items
    end
    
    def remove_system_classes(class_list)
      class_list.reject { |m|
	name = (m =~ /^(\w+)/) ? $1 : m
	@@system_names[name]
      }
    end
    
    # Class attributes and methods -------------------------------------
    
    @@system_names = {}
    
    def ClassListModel.record_system_modules
      system_modules = []
      ObjectSpace.each_object (Module) { |m| system_modules << m }
      system_modules << TkVirtualEvent
      
      system_modules.each { |m| @@system_names[m.to_s] = true }
    end
    
  end
  
  ####################################################################
  class MethodListModel < ListModel
    
    def initialize (finder)
      super()
      @finder = finder
    end
    
    private
    
    def update_items
      @items = @finder.methods(@source)
    end
  end
  
  ####################################################################
  class ModuleListModel < ListModel
    
    private
    
    def update_items
      @items = class_for(@source).included_modules.sort
    end
  end
  
  
  ####################################################################
  class ChildListModel < ListModel
    
    private
    
    def update_items
      classes = []
      ObjectSpace.each_object(Class) { |c| classes << c }
      parent = class_for(@source)
      @items = classes.select { |c| child_of?(parent, c) }
      @items.sort! {|a,b| a.to_s <=> b.to_s}
    end
    
    def child_of? (parent, candidate_child)
      return (candidate_child.superclass == parent or
	      candidate_child.included_modules.member?(parent))
    end
  end
  
  
  ####################################################################
  class ClassBrowserModel
    attr_reader :class_model, :child_model, :module_model, :method_model

    def initialize (finder)
      @class_model = ClassListModel.new
      @child_model = ChildListModel.new
      @module_model = ModuleListModel.new
      @method_model = MethodListModel.new (finder)

      @method_model.use_class_model(class_model)
      @child_model.use_class_model(class_model)
      @module_model.use_class_model(class_model)

      @child_model.selection_monitor  = @class_model
      @module_model.selection_monitor = @class_model
    end
      
    def fill
      @class_model.fill_from ("")
    end

  end
  
end # end of GEM module

