module Innate

  # Acts as name-space for helpers
  module Helper
    # Public instance methods of helpers in here will be recognized as actions
    LOOKUP = EXPOSE = Set.new

    # Usually called from Innate::Node::included
    # We also include Innate::Trinity here, as it may be needed in models when
    # you use helper methods there.
    def self.included(into)
      into.extend(HelperAccess)
      into.__send__(:include, Trinity)
      into.helper(*HelpersHelper.options[:default])
    end
  end

  # Provides access to #helper method without polluting the name-space any
  # further.
  module HelperAccess

    # Convenience method used by Innate::Node.
    #
    # Usage:
    #
    #   class Hi
    #     extend Innate::HelperAccess
    #     helper :cgi, :link, :aspect
    #   end
    #
    # This will require the helpers and call:
    #
    #     Hi.include(Innate::Helper::CGI)
    #     Hi.include(Innate::Helper::Link)
    #     Hi.include(Innate::Helper::Aspect)
    def helper(*helpers)
      HelpersHelper.each_include(self, *helpers)
    end
  end

  # Here come the utility methods used from the HelperAccess#helper method, we
  # do this to keep method count at a minimum and because HelpersHelper is such
  # an awesome name that just can't be wasted.
  #
  # Usage if you want to only extend with helpers:
  #
  #   class Hi
  #     Innate::HelpersHelper.each_extend(self, :cgi, :link, :aspect)
  #   end
  #
  # Usage if you only want to include helpers:
  #
  #   class Hi
  #     Innate::HelpersHelper.each_include(self, :cgi, :link, :aspect)
  #   end
  #
  # Usage for iteration:
  #
  #   Innate::HelpersHelper.each(:cgi, :link, :aspect) do |mod|
  #     p mod
  #   end
  #
  # Usage for translating helpers to modules:
  #
  #   p Innate::HelpersHelper.each(:cgi, :link, :aspect)
  module HelpersHelper
    include Optioned

    options.dsl do
      o "Paths that will be searched for helper files",
        :paths, [Dir.pwd, File.dirname(__FILE__)]

      o "Namespaces that will be searched for helper modules",
        :namespaces, [Helper]

      o "Filename extensions considered for helper files",
        :exts, %w[rb so bundle]

      o "Default helpers, added on inclusion of the Helper module",
        :default, [:aspect, :cgi, :flash, :link, :render, :redirect]
    end

    EXTS = %w[rb so bundle]

    # all of the following are singleton methods

    module_function

    # Yield all the modules we can find for the given names of helpers, try to
    # require them if not available.
    #
    # NOTE: Unlike usual #each, this will actually return an Array of the found
    #       modules instead of the given +*names+
    #
    #
    # Usage:
    #
    #   Innate::HelpersHelper.each(:cgi, :link, :aspect) do |mod|
    #     p mod
    #   end

    def each(*names)
      names.map do |name|
        if name.class == Module
          yield(name) if block_given?
          name
        elsif mod = get(name)
          yield(mod) if block_given?
          mod
        elsif try_require(name)
          redo
        else
          raise LoadError, "Helper #{name} not found"
        end
      end
    end

    # Shortcut to extend +into+ with Helper modules corresponding to +*names+.
    # +into+ has to respond to #extend.
    #
    # Usage:
    #
    #   class Hi
    #     Innate::HelpersHelper.each_extend(self, :cgi, :link, :aspect)
    #   end
    def each_extend(into, *names, &block)
      return if names.empty?
      into.extend(*each(*names, &block))
    end

    # Shortcut to include Helper modules corresponding to +*names+ on +into+.
    # +into+ has to respond to #include.
    # #__send__(:include) is used in case #include raises due to being
    # private/protected
    #
    # in case #include is a private/protected method.
    #
    # Usage:
    #
    #   class Hi
    #     Innate::HelpersHelper.each_include(self, :cgi, :link, :aspect)
    #   end
    def each_include(into, *names, &block)
      return if names.compact.empty?
      into.__send__(:include, *each(*names, &block))
    end

    # Based on a simple set of rules we will first construct the most likely
    # name for the helper and then grep the constants in the Innate::Helper
    # module for any matches.
    #
    # helper :foo_bar # => FooBar
    # helper :foo # => Foo
    def get(name)
      module_name = /^#{name.to_s.dup.delete('_')}(?:helper)?$/i

      options.namespaces.each do |namespace|
        found = namespace.constants.grep(module_name).first
        return namespace.const_get(found) if found
      end

      nil
    end

    # Figure out files that might have the helper we ask for and then require
    # the first we find, if any.
    def try_require(name)
      if found = find_helper(name.to_s)
        require(found) || true
      else
        raise(LoadError, "Helper #{name} not found")
      end
    end

    def find_helper(name)
      options.paths.uniq.find do |path|
        base = ::File.join(path, 'helper', name)
        options.exts.find do |ext|
          full = "#{base}.#{ext}"
          return full if ::File.file?(full)
        end
      end
    end
  end
end
