module Sequel
  # Empty namespace that plugins should use to store themselves,
  # so they can be loaded via Model.plugin.
  #
  # Plugins should be modules with one of the following conditions:
  # * A singleton method named apply, which takes a model, 
  #   additional arguments, and an optional block.  This is called
  #   once, the first time the plugin is loaded, with the arguments
  #   and block provide to the call to Model.plugin.
  # * A module inside the plugin module named InstanceMethods,
  #   which will be included in the model class.
  # * A module inside the plugin module named ClassMethods,
  #   which will extend the model class.
  # * A module inside the plugin module named DatasetMethods,
  #   which will extend the model's dataset.
  # * A singleton method named configure, which takes a model, 
  #   additional arguments, and an optional block.  This is called
  #   every time the Model.plugin method is called.
  module Plugins
  end
  
  class Model
    # Loads a plugin for use with the model class, passing optional arguments
    # to the plugin.  If the plugin is a module, load it directly.  Otherwise,
    # require the plugin from either sequel/plugins/#{plugin} or
    # sequel_#{plugin}, and then attempt to load the module using a
    # the camelized plugin name under Sequel::Plugins.
    def self.plugin(plugin, *args, &blk)
      arg = args.first
      block = args.length > 1 ? lambda{args} : lambda{arg}
      m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
      unless @plugins.include?(m)
        @plugins << m
        m.apply(self, *args, &blk) if m.respond_to?(:apply)
        if m.const_defined?("InstanceMethods")
          define_method(:"#{plugin}_opts", &block)
          include(m::InstanceMethods)
        end
        if m.const_defined?("ClassMethods")
          meta_def(:"#{plugin}_opts", &block)
          extend(m::ClassMethods)
        end
        if m.const_defined?("DatasetMethods")
          if @dataset
            dataset.meta_def(:"#{plugin}_opts", &block)
            dataset.extend(m::DatasetMethods)
          end
          dataset_method_modules << m::DatasetMethods
          meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
          def_dataset_method(*meths) unless meths.empty?
        end
      end
      m.configure(self, *args, &blk) if m.respond_to?(:configure)
    end
    
    module ClassMethods
      # Array of plugins loaded by this class
      attr_reader :plugins
      
      private
  
      # Returns the new style location for the plugin name.
      def plugin_gem_location(plugin)
        "sequel/plugins/#{plugin}"
      end

      # Returns the old style location for the plugin name.
      def plugin_gem_location_old(plugin)
        "sequel_#{plugin}"
      end
  
      # Returns the module for the specified plugin. If the module is not 
      # defined, the corresponding plugin gem is automatically loaded.
      def plugin_module(plugin)
        module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
        if not Sequel::Plugins.const_defined?(module_name)
          begin
            require plugin_gem_location(plugin)
          rescue LoadError
            require plugin_gem_location_old(plugin)
          end
        end
        Sequel::Plugins.const_get(module_name)
      end
    end
  end
end
