# A module for loading subclasses into an array and retrieving
# them by name.  Also sets up a method for each class so
# that you can just do Klass.subclass, rather than Klass.subclass(:subclass).
#
# This module is currently used by network handlers and clients.
module Puppet::Util::SubclassLoader
  attr_accessor :loader, :classloader

  # Iterate over each of the subclasses.
  def each
    @subclasses ||= []
    @subclasses.each { |c| yield c }
  end

  # The hook method that sets up subclass loading.  We need the name
  # of the method to create and the path in which to look for them.
  def handle_subclasses(name, path)
    raise ArgumentError, "Must be a class to use SubclassLoader" unless self.is_a?(Class)
    @subclasses = []

    @loader = Puppet::Util::Autoload.new(self, path, :wrap => false)

    @subclassname = name

    @classloader = self

    # Now create a method for retrieving these subclasses by name.  Note
    # that we're defining a class method here, not an instance.
    meta_def(name) do |subname|
      subname = subname.to_s.downcase

      unless c = @subclasses.find { |c| c.name.to_s.downcase == subname }
        loader.load(subname)
        c = @subclasses.find { |c| c.name.to_s.downcase == subname }

        # Now make the method that returns this subclass.  This way we
        # normally avoid the method_missing method.
        define_method(subname) { c } if c and ! respond_to?(subname)
      end
      return c
    end
  end

  # Add a new class to our list.  Note that this has to handle subclasses of
  # subclasses, thus the reason we're keeping track of the @@classloader.
  def inherited(sub)
    @subclasses ||= []
    sub.classloader = self.classloader
    if self.classloader == self
      @subclasses << sub
    else
      @classloader.inherited(sub)
    end
  end

  # See if we can load a class.
  def method_missing(method, *args)
    unless self == self.classloader
      super
    end
    return nil unless defined?(@subclassname)
    self.send(@subclassname, method) || nil
  end

  # Retrieve or calculate a name.
  def name(dummy_argument=:work_arround_for_ruby_GC_bug)
    @name ||= self.to_s.sub(/.+::/, '').intern

    @name
  end

  # Provide a list of all subclasses.
  def subclasses
    @loader.loadall
    @subclasses.collect { |klass| klass.name }
  end
end