module Treat::Workers::Groupable

  # Lazily load the worker classes in the group.
  def const_missing(const)
    bits = self.ancestors[0].to_s.split('::')
    bits.collect! { |bit| bit.ucc }
    file = bits.join('/') + "/#{const.ucc}"
    path = Treat.paths.lib + "#{file}.rb"
    if not File.readable?(path)
      raise Treat::Exception,
      "File '#{file}.rb' corresponding to " +
      "requested worker #{self}::#{const} " +
      "does not exist."
    else
      require file
      if not self.const_defined?(const)
        raise Treat::Exception,
        "File #{file}.rb does not define " +
        "#{self}::#{const}."
      end
      const_get(const)
    end
  end

  # Cache the list of workers to improve performance.
  @@list = {}
  # Populates once the list of the workers in the group
  # by crawling the filesystem.
  def list
    mod = self.mn.ucc
    if @@list[mod].nil?
      @@list[mod] = []
      dirs = Dir[Treat.paths.lib + "treat/workers/*/#{mod}/*.rb"]
      dirs.each do |file|
        @@list[mod] <<
        file.split('/')[-1][0..-4].intern
      end
    end
    @@list[mod]
  end

  # Boolean - does the group have the supplied class
  # included in its targets?
  def has_target?(target, strict = false)
    is_target = false
    self.targets.each do |entity_type|
      t = entity_type.cc
      entity_type = Treat::Entities.const_get(t)
      if target < entity_type ||
        entity_type == target
        is_target = true; break
      end
    end
    is_target
  end

  # Create a new algorithm within the group. Once
  # the algorithm is added, it will be automatically
  # installed on all the targets of the group.
  def add(class_name, &block)
    c = class_name.cc.intern
    klass = self.const_set(c, Class.new)
    method = self.method
    @@list[self.mn.ucc] << class_name
    klass.send(:define_singleton_method,
    method) do |entity, options={}|
      block.call(entity, options)
    end
  end

  # Get constants in this module, excluding by
  # default those defined by parent modules.
  def const_get(const); super(const, false); end

  # Modify the extended class.
  def self.extended(group)

    group.module_eval do
      
      class << self
        
        # The type of the group. There are three types:
        #
        # - Transformers transform the tree of an entity.
        # - Annotators compute a value and store it in the entity.
        # - Computers compute a value and do not store it.
        attr_accessor :type
        # The default worker in the group, for language-
        # independent tasks.
        attr_accessor :default
        # The entity types which the group's workers work on.
        attr_accessor :targets
        # Presets to automatically generate functions.
        attr_accessor :presets
        # The preset option to use with preset functions.
        attr_accessor :preset_option
        # Whether to recurse within multiple targets or not.
        attr_accessor :recursive
      end
      
      self.recursive = false
      self.list
      
      # Return the method corresponding to the group.
      # This method resolves the name of the method
      # that a group should provide based on the name
      # of the group. Basically, if the group ends in
      # -ers, the verb corresponding to the group is
      # returned (tokenizers -> tokenize, inflectors ->
      # inflect). Otherwise, the name of the method
      # is the same as that of the group (encoding ->
      # encoding, tag -> tag).
      @method = nil
      def self.method
        return @method if @method
        m = self.mn.ucc.dup
        if  m[-4..-1] == 'zers'
          if type == :annotator
            m[-5..-1] = m[-6] == 'l' ? '' : 'y'
          else
            m = m[0..-3]
          end
          n = m         
        elsif m[-4..-1] == 'iers'
          m[-4..-1] = 'y'
          n = m
        elsif m[-3..-1] == 'ers'
          if ['k', 't', 'm', 'd', 
              'g', 'n', 'x', 'h'].
              include? m[-4]
            n = m[0..-4]
            if n[-1] == n[-2]
              n = n[0..-2] 
            end
          else
            n = m[0..-3]
          end
        elsif m[-3..-1] == 'ors'
          n = m[0..-4] + 'e'
        else
          n = m
        end
        @method = n.intern
      end
      
    end
    
  end
  
end