module Taggable
  class RedirectRequired < StandardError
    def initialize(message = nil); super end
  end
  
  module FacetedPage
    
    # This module can be mixed into any Page subclass to give it mystical web 2.0 faceting powers.
    # Any nonexistent child path is assumed to be a tag set, and methods are provided for reading
    # that set, and adding and removing tags.
    #
    def self.included(base)
      base.extend ClassMethods
      base.class_eval {
        include InstanceMethods
        alias_method_chain :path, :tags
      }
    end
    
    module ClassMethods
    end

    module InstanceMethods

      # Faceted pages map nicely onto urls and are cacheable. 
      # There can be redundancy if tags are specified in varying order,
      # but the site controller tries to normalize everything.
      #
      def cache?
        true
      end

      # We override the normal Page#find_by_path mechanism and treat nonexistent child paths
      # are understood as tag sets. Note that the extended site_controller will add to this set
      # any tags that have been supplied in query string parameters. This may trigger a redirect
      # to bring the request path into line with the consolidated tag set.
      #
      def find_by_path(path, live = true, clean = false)
        path = clean_path(path) if clean
        return false unless path =~ /^#{Regexp.quote(self.path)}(.*)/
        tags = $1.split('/')
        if slug_child = children.find_by_slug(tags[0])
          found = slug_child.find_by_url(path, live, clean)
          return found if found
        end
        remove_tags, add_tags = tags.partition{|t| t.first == '-'}
        add_request_tags(add_tags)
        remove_request_tags(remove_tags)
        self
      end
      alias_method :find_by_url, :find_by_path

      # The set of tags attached to the page request.
      #
      def requested_tags
        @requested_tags ||= []
      end

      # The normal `path` method is extended to append the (sorted and deduped) tags attached to the page request
      #
      def path_with_tags(tags = requested_tags)
        clean_path( path_without_tags + '/' + tags.uniq.compact.sort.map(&:clean_title).to_param )
      end

    private
  
      # @requested_tags is the set of Tag objects attached to the page request.
      #
      def requested_tags=(tags)
        @requested_tags = tags
      end

      # We hold in memory a list of the tags that were appended to the path when this page was selected.
      # This method adds tags to that list. It is normally called only once, to populate the list.
      #
      def add_request_tags(tags=[])
        if tags.any?
          tags.collect! { |tag| Tag.find_by_title(Rack::Utils::unescape(tag)) }
          self.requested_tags = (self.requested_tags + tags.select{|t| !t.nil?}).uniq
        end
      end

      # This method removes tags from the appended list. Normally only used to handle defaceting links
      # (in the form /path/to/page/tag1/tag2/tag3/-tag2).
      #
      def remove_request_tags(tags=[])
        if tags.any?
          tags.collect! { |tag|
            tag.slice!(0) if tag.first == '-' 
            Tag.find_by_title(Rack::Utils::unescape(tag)) 
          }
          self.requested_tags = (self.requested_tags - tags.compact).uniq
        end
      end
    end

  end
end