module ActionController #:nodoc:
  class Base
  
    #
    # Build the fragment key from a particular context. This must be deterministic 
    # and stateful except for the tag. We can't scope the key to arbitrary params 
    # because the view doesn't have access to which are relevant and which are 
    # not.
    #
    # Note that the tag can be pretty much any object. Define #to_interlock_tag
    # if you need custom tagging for some class. ActiveRecord::Base already
    # has it defined appropriately.
    #
    # If you pass an Array of symbols as the tag, it will get value-mapped onto
    # params and sorted. This makes granular scoping easier, although it doesn't
    # sidestep the normal blanket invalidations.
    #
    def caching_key(ignore = nil, tag = nil)
      ignore = Array(ignore)
      ignore = Interlock::SCOPE_KEYS if ignore.include? :all    
      
      if (Interlock::SCOPE_KEYS - ignore).empty? and !tag
        raise UsageError, "You must specify a :tag if you are ignoring the entire default scope."
      end
        
      if tag.is_a? Array and tag.all? {|x| x.is_a? Symbol}
        tag = tag.sort_by do |key|
          key.to_s
        end.map do |key| 
          params[key].to_interlock_tag
        end.join(";")
      end
      
      Interlock.caching_key(      
        ignore.include?(:controller) ? 'any' : controller_name,
        ignore.include?(:action) ? 'any' : action_name,
        ignore.include?(:id) ? 'all' : params[:id],
        tag
      )
    end
    
    #
    # Mark a controller block for caching. Accepts a list of class dependencies for
    # invalidation, as well as a :tag key for explicit fragment scoping.
    #
    def behavior_cache(*args)  
      conventional_class = begin; controller_name.classify.constantize; rescue NameError; end
      options, dependencies = Interlock.extract_options_and_dependencies(args, conventional_class)
      
      raise UsageError, ":ttl has no effect in a behavior_cache block" if options[:ttl]
      
      key = caching_key(options.value_for_indifferent_key(:ignore), options.value_for_indifferent_key(:tag))      
      Interlock.register_dependencies(dependencies, key)
          
      # See if the fragment exists, and run the block if it doesn't.
      unless read_fragment(key)    
        Interlock.say key, "is running the controller block"
        yield
      end
    end
    
    #:stopdoc:
    alias :caching :behavior_cache # Deprecated
    #:startdoc:

    private

    # 
    # Callback to reset the local cache.
    #
    def clear_interlock_local_cache
      Interlock.local_cache = ActionController::Base::MemoryStore.new
      RAILS_DEFAULT_LOGGER.warn "** cleared interlock local cache"
    end    
    
    # Should be registered first in the chain
    prepend_before_filter :clear_interlock_local_cache 
  
  end
  
  module Caching #:nodoc:
    module Fragments
       
      #
      # Replaces Rail's write_fragment method. Avoids extra checks for regex keys, 
      # which are unsupported, adds more detailed logging information, and stores 
      # writes in the local process cache too to avoid duplicate memcached requests.
      #
      def write_fragment(key, content, options = nil)
        return unless perform_caching

        fragment_cache_store.write(key, content, options)
        Interlock.local_cache.write(key, content, options)

        Interlock.say key, "wrote"

        content
      end

      #
      # Replaces Rail's read_fragment method. Avoids checks for regex keys, 
      # which are unsupported, adds more detailed logging information, and
      # checks the local process cache before hitting memcached. Hits on 
      # memcached are then stored back locally to avoid duplicate requests.
      #
      def read_fragment(key, options = nil)
        return unless perform_caching

        if content = Interlock.local_cache.read(key, options)
          # Interlock.say key, "read from local cache"
        elsif content = fragment_cache_store.read(key, options)            
          Interlock.say key, "read from memcached"
          Interlock.local_cache.write(key, content, options)
        else
          # Interlock.say key, "not found"
        end
        content
      end      
      
    end
  end    
end