# -*- encoding: utf-8 -*-

require 'webgen/extension_manager'
require 'webgen/path'

module Webgen

  # Namespace for all classes that provide source paths.
  #
  # == About
  #
  # This class is used to manage source classes. A source class is a class that returns a set of
  # Path objects when the \#paths method is called. For example, the FileSystem class uses the file
  # system as the source for the source paths.
  #
  # The #register method is used for registering new source classes.
  #
  # To get all configured source paths, use the #paths method of this class.
  #
  # Extension writers may be interested in the #passive_sources accessor which allows one to
  # register sources that provide paths that are only used when actually referenced.
  #
  # == Implementing a source class
  #
  # A source class only needs to respond to the method +paths+ which needs to return a set of Path
  # objects for the source. It is expected that after a source object is initialized it always
  # returns the same path objects. The +initialize+ method of a source class must take the website
  # object as first argument and may take an arbitrary number of additional arguments.
  #
  # Note that the returned Path objects need to have the meta information 'modified_at' set to the
  # correct last modification time of the path, i.e. the value has to be a Time object! This is
  # needed to determine whether the content of the path has been changed since the last invocation
  # of webgen.
  #
  # == Sample source class
  #
  # Following is a simple source class which stores the paths and their contents in a hash:
  #
  #   require 'stringio'
  #   require 'set'
  #   require 'webgen/path'
  #
  #   class MemorySource
  #
  #     CONTENT = {
  #       '/directory/' => nil,
  #       '/directory/file.page' => "This is the content of the file"
  #     }
  #
  #     def initialize(website, data = {})
  #       @data = data
  #     end
  #
  #     def paths
  #       CONTENT.merge(@data).collect do |path, content|
  #         Webgen::Path.new(path, 'modified_at' => Time.now) { StringIO.new(content.to_s) }
  #       end.to_set
  #     end
  #
  #   end
  #
  #   website.ext.source.register MemorySource, :name => 'memory'
  #
  class Source

    include Webgen::ExtensionManager


    # An array with one or more passive source definitions (a source definition is an array
    # containing a mount point, the short name for a Source class and its arguments).
    #
    # The paths read from these sources will automatically be tagged with the 'passive' meta
    # information key so that they are only used when referenced.
    #
    # This is very useful for providing templates, images and other paths in webgen extensions that
    # should only be rendered when actually being referenced.
    attr_reader :passive_sources


    # Create a new source manager object for the given website.
    def initialize(website)
      super()
      @website = website
      @passive_sources = []
    end

    # Register a source class.
    #
    # The parameter +klass+ has to contain the name of the source class or the class object itself.
    # If the class is located under this namespace, only the class name without the hierarchy part
    # is needed, otherwise the full class name including parent module/class names is needed.
    #
    # === Options:
    #
    # [:name] The name for the source. If not set, it defaults to the snake-case version (i.e.
    #         FileSystem → file_system) of the class name (without the hierarchy part). It should
    #         only contain letters.
    #
    # === Examples:
    #
    #   source.register('FileSystem')   # registers Webgen::Source::FileSystem
    #
    #   source.register('::FileSystem') # registers FileSystem !!!
    #
    #   source.register('MyModule::Doit', name: 'my_doit')
    #
    def register(klass, options={}, &block)
      do_register(klass, options, false, &block)
    end

    # Return all configured source paths.
    #
    # The source paths are taken from the sources specified in the "sources" configuration option
    # and from passive sources array (see #passive_sources). All paths that additionally match one
    # of the "sources.ignore_paths" patterns are ignored.
    def paths
      if !defined?(@paths)
        active_source = extension('stacked').new(@website, @website.config['sources'].collect do |mp, name, *args|
                                                   [mp, extension(name).new(@website, *args)]
                                                 end)
        passive_source = extension('stacked').new(@website, @passive_sources.collect do |mp, name, *args|
                                                    [mp, extension(name).new(@website, *args)]
                                                  end)
        passive_source.paths.each {|path| path['passive'] = true}
        source = extension('stacked').new(@website, [['/', active_source], ['/', passive_source]])

        @paths = []
        source.paths.each do |path|
          if !(@website.config['sources.ignore_paths'].any? {|pat| Webgen::Path.matches_pattern?(path, pat)})
            @paths << path
          end
        end
      end
      @paths
    end

  end

end
