require 'rack/cache/key'
require 'rack/cache/storage'

module Rack::Cache

  # Configuration options and utility methods for option access. Rack::Cache
  # uses the Rack Environment to store option values. All options documented
  # below are stored in the Rack Environment as "rack-cache.<option>", where
  # <option> is the option name.
  module Options
    extend self

    def self.option_accessor(key)
      name = option_name(key)
      define_method(key) { || options[name] }
      define_method("#{key}=") { |value| options[name] = value }
      define_method("#{key}?") { || !! options[name] }
    end

    def option_name(key)
      case key
      when Symbol ; "rack-cache.#{key}"
      when String ; key
      else raise ArgumentError
      end
    end
    module_function :option_name

    # Enable verbose trace logging. This option is currently enabled by
    # default but is likely to be disabled in a future release.
    option_accessor :verbose

    # The storage resolver. Defaults to the Rack::Cache.storage singleton instance
    # of Rack::Cache::Storage. This object is responsible for resolving metastore
    # and entitystore URIs to an implementation instances.
    option_accessor :storage

    # A URI specifying the meta-store implementation that should be used to store
    # request/response meta information. The following URIs schemes are
    # supported:
    #
    # * heap:/
    # * file:/absolute/path or file:relative/path
    # * memcached://localhost:11211[/namespace]
    #
    # If no meta store is specified the 'heap:/' store is assumed. This
    # implementation has significant draw-backs so explicit configuration is
    # recommended.
    option_accessor :metastore

    # A custom cache key generator, which can be anything that responds to :call.
    # By default, this is the Rack::Cache::Key class, but you can implement your
    # own generator. A cache key generator gets passed a request and generates the
    # appropriate cache key.
    #
    # In addition to setting the generator to an object, you can just pass a block
    # instead, which will act as the cache key generator:
    #
    #   set :cache_key do |request|
    #     request.fullpath.replace(/\//, '-')
    #   end
    option_accessor :cache_key

    # A URI specifying the entity-store implementation that should be used to
    # store response bodies. See the metastore option for information on
    # supported URI schemes.
    #
    # If no entity store is specified the 'heap:/' store is assumed. This
    # implementation has significant draw-backs so explicit configuration is
    # recommended.
    option_accessor :entitystore

    # The number of seconds that a cache entry should be considered
    # "fresh" when no explicit freshness information is provided in
    # a response. Explicit cache-control or expires headers
    # override this value.
    #
    # Default: 0
    option_accessor :default_ttl

    # Set of response headers that are removed before storing them in the
    # cache. These headers are only removed for cacheable responses.  For
    # example, in most cases, it makes sense to prevent cookies from being
    # stored in the cache.
    #
    # Default: ['set-cookie']
    option_accessor :ignore_headers

    # Set of request headers that trigger "private" cache-control behavior
    # on responses that don't explicitly state whether the response is
    # public or private via a cache-control directive. Applications that use
    # cookies for authorization may need to add the 'Cookie' header to this
    # list.
    #
    # Default: ['Authorization', 'Cookie']
    option_accessor :private_headers

    # Specifies whether a client can force cache reload by including a
    # cache-control "no-cache" directive in the request. Disabled by default.
    option_accessor :allow_reload

    # Specifies whether a client can force cache revalidate by including a
    # cache-control "max-age=0" directive in the request. Disabled by default.
    option_accessor :allow_revalidate

    # Specifies whether the underlying entity store's native expiration should
    # be used.
    option_accessor :use_native_ttl

    # Specifies whether to serve a request from a stale cache entry if
    # the attempt to revalidate the cache entry raises an exception.
    option_accessor :fault_tolerant

    # The underlying options Hash. During initialization (or outside of a
    # request), this is a default values Hash. During a request, this is the
    # Rack environment Hash. The default values Hash is merged in underneath
    # the Rack environment before each request is processed.
    def options
      @env || @default_options
    end

    # Set multiple options.
    def options=(hash={})
      hash.each { |key,value| write_option(key, value) }
    end

    # Set an option. When +option+ is a Symbol, it is set in the Rack
    # Environment as "rack-cache.option". When +option+ is a String, it
    # exactly as specified. The +option+ argument may also be a Hash in
    # which case each key/value pair is merged into the environment as if
    # the #set method were called on each.
    def set(option, value=self, &block)
      if block_given?
        write_option option, block
      elsif value == self
        self.options = option.to_hash
      else
        write_option option, value
      end
    end

  private
    def initialize_options(options={})
      @default_options = {
        'rack-cache.cache_key'        => Key,
        'rack-cache.verbose'          => true,
        'rack-cache.storage'          => Rack::Cache::Storage.instance,
        'rack-cache.metastore'        => 'heap:/',
        'rack-cache.entitystore'      => 'heap:/',
        'rack-cache.default_ttl'      => 0,
        'rack-cache.ignore_headers'   => ['set-cookie'],
        'rack-cache.private_headers'  => ['Authorization', 'Cookie'],
        'rack-cache.allow_reload'     => false,
        'rack-cache.allow_revalidate' => false,
        'rack-cache.use_native_ttl'   => false,
        'rack-cache.fault_tolerant'   => false,
      }
      self.options = options
    end

    def read_option(key)
      options[option_name(key)]
    end

    def write_option(key, value)
      options[option_name(key)] = value
    end
  end
end
