module Sprout

##
# This class represents a Feature that is written in
# Ruby code that exists on the other side of the
# Ruby load path.
#
# The idea here, is that one can +include+ the Sprout::RubyFeature
# module into their concrete class, and then accept
# requests to +register+ and +load+ from clients that
# are interested in pluggable features.
#
# An example is as follows:
#
#     require 'sprout'
#
#     class MyClass
#       include Sprout::RubyFeature
#     end
#
# In some other Ruby file:
#
#     MyClass.load :other, 'other_gem', '>= 1.0pre'
#
# In the desired Ruby file:
#
#     class OtherClass
#       include Sprout::Executable
#
#       set :name, :other
#       set :pkg_name, 'other_gem'
#       set :pkg_version, '1.0.pre'
#
#       # do something...
#     end
#
#     MyClass.register OtherClass.new
#
module RubyFeature

  extend Concern

  module ClassMethods

    ##
    # Register a new feature for future lookups
    def register entity
      validate_registration entity
      registered_entities.unshift entity
      entity
    end

    ##
    # Load a feature by name.
    def load name_or_names, pkg_name=nil, version_requirement=nil
      entity = entity_for name_or_names, pkg_name, version_requirement
      if(entity.nil?)
        # Try to require the pkg_name:
        require_ruby_package pkg_name unless pkg_name.nil?
        # Update any entities that registered from our require:
        update_registered_entities
        # search for the requested entity:
        entity = entity_for name_or_names, pkg_name, version_requirement
      end

      if(entity.nil?)
        message = "The requested entity: (#{name_or_names}) with pkg_name: (#{pkg_name}) and version: "
        message << "(#{version_requirement}) does not appear to be loaded."
        message << "\n"
        message << "We did find (#{registered_entities.size}) registered entities in that package:\n\n"
        registered_entities.each do |other|
          message << ">> name: (#{other.name}) pkg_name: (#{other.pkg_name}) pkg_version: (#{other.pkg_version})\n"
        end
        message << "\n\nYou may need to update your Gemfile and run 'bundle install' "
        message << "to update your local gems.\n\n"
        raise Sprout::Errors::LoadError.new message
      end

      resolve_file_target entity
      entity
    end

    def clear_entities!
      @registered_entities = []
    end

    protected

    def resolve_file_target entity
      if(entity.respond_to? :resolve)
        entity.resolve
      elsif(entity.respond_to? :file_target)
        file_target = entity.file_target
        if(file_target.respond_to? :resolve)
          file_target.resolve
        end
      end
    end

    def validate_registration entity
      if(!entity.respond_to?(:name) || entity.name.nil?)
        raise Sprout::Errors::UsageError.new "Cannot register a RubyFeature without a 'name' getter"
      end
    end

    #
    # Used by the Generator::Base to update inputs from 
    # empty class definitions to instances..
    def update_registered_entities
    end

    def entity_for name_or_names, pkg_name, version_requirement
      # These commented blocks help immensely when debugging
      # loading and registration issues, please leave them here:
      #puts "+++++++++++++++++++++++++++"
      #puts ">> entity_for #{name_or_names} pkg_name: #{pkg_name} version: #{version_requirement}"
      #registered_entities.each do |entity|
        #puts ">> entity: #{entity.name} platform: #{entity.platform} pkg_name: #{entity.pkg_name} version: #{entity.pkg_version}"
      #end
      registered_entities.reverse.select do |entity|
          satisfies_name?(entity, name_or_names) && 
          satisfies_platform?(entity) &&
          satisfies_pkg_name?(entity, pkg_name) &&
          satisfies_version?(entity, version_requirement)
      end.first
    end

    def satisfies_environment? entity, environment
      #puts ">> env: #{entity.environment} vs. #{environment}"
      environment.nil? || !entity.respond_to?(:environment) || entity.environment.to_s == environment.to_s
    end

    def satisfies_pkg_name? entity, expected
      #puts ">> pkg_name: #{entity.pkg_name} vs. #{expected}"
      expected.nil? || !entity.respond_to?(:pkg_name) || entity.pkg_name.to_s == expected.to_s
    end

    def satisfies_name? entity, expected
      #puts ">> name: #{entity.name} vs. #{expected}"
      return true if expected.nil? || !entity.respond_to?(:name)
      if expected.is_a?(Array)
        return expected.include? entity.name
      end
      return expected.to_s == entity.name.to_s
    end

    def satisfies_platform? entity
      #puts">> satisfies platform?"
      # If not platform is defined, it's assumed to work on all platforms:
      return true if !entity.respond_to?(:platform) || entity.platform.nil?
      #puts ">> platform: #{entity.platform}"
      Sprout.current_system.can_execute?(entity.platform)
    end

    def satisfies_version? entity, version_requirement=nil
      return true if version_requirement.nil?
      req_version = Gem::Requirement.create version_requirement
      req_version.satisfied_by?(Gem::Version.create(entity.pkg_version))
    end

    def require_ruby_package name
      begin
        require name.to_s
      rescue LoadError => e
        raise Sprout::Errors::LoadError.new "Could not load the required file (#{name}) - Maybe you need to run 'gem install #{name}' or maybe 'bundle install'?"
      end
    end

    ##
    # An entity has the following parameters:
    # name
    # pkg_name
    # pkg_version
    # platform
    #
    def registered_entities
      @registered_entities ||= []
    end
  end

end

end

## # Mixin on the Rake::Task so # that our concrete entities # can interoperate with some # knowledge of each other. class Rake::Task

attr_accessor :sprout_entity

end