module Pancake
  module Mixins
    module Publish
      class ActionOptions
        attr_reader :params, :formats, :default
        CONFIG_OPTIONS = [:provides, :only_provides]
      
        # Generates a new instance of the class. This instance encapsulates the
        # options and logic needed to validate the parameters being input into
        # a controller action. First argument is a list of symbols for the 
        # formats it supports. The second is hash of options. Typically this 
        # hash is generated by the publish declaration in a controller.
        #
        # TODO: Allow params to be grouped together
        #
        # @api private
        def initialize(default_formats, opts)
         # Extract the params — excluding configuration — and turn the keys into
         # strings. Additionally, check to see if someone has passed in a a raw
         # value rather than using one of our as_* methods.
         @params = opts.inject({}) do |memo, opt|
           unless CONFIG_OPTIONS.include? opt[0]
             unless opt[1].is_a?(Array)
               raise "Parameter values must be specified with an as_* method. You passed in a #{opt[1].class.to_s}"
             end
             memo[opt[0].to_s] = opt[1] 
           end
           memo
         end     
          @formats = extract_format_options(default_formats, opts)
        end

        # Accepts a hash of parameters and replaces each entry with it’s
        # coerced or default value. If an expected param is missing, it will
        # raise an error.
        #
        # TODO: Flag missing params rather than just raising an error 
        # TODO: Allow defaults to be dynamically generated, i.e. method call
        #
        # @api private
        def validate_and_coerce(incoming_params)
          missing = []
          params.each do |name, config|
            type, default, opts = config
            value = incoming_params[name]

            if !value.nil? && value != ""
              incoming_params[name] = send("validate_and_coerce_#{type}", value, opts)
            else
              if default == :req
                missing << [name, type]
              elsif default != :opt
                incoming_params[name] = default
              end
            end
          end
          [incoming_params, missing]
        end
      
        private
      
        # Accepts a string and turns it into an integer. The opts argument is
        # only there to satisfy the interface. it is not used.
        #
        # @api private
        def validate_and_coerce_integer(value, opts)
          value.to_i
        end

        # Accepts a string and turns it into a date. The opts argument is
        # only there to satisfy the interface. it is not used.
        #
        # @api private
        def validate_and_coerce_date(value, opts)
          Date.parse(value)
        end
      
        # Turns the incoming value into a string. The opts argument is
        # only there to satisfy the interface. it is not used.
        #
        # @api private
        def validate_and_coerce_string(value, opts)
          value.to_s
        end

        # Extracts the format options from a hash. It is used to compile the 
        # list of formats that an action can provide. It accepts an array of
        # symbols representing the base formats and will add to or overwrite 
        # them based on the options in the hash.
        #
        # @api private
        def extract_format_options(defaults, opts)
          @formats = if opts[:provides]
            defaults + [opts[:provides]].flatten
          elsif opts[:only_provides]
            [opts[:only_provides]].flatten
          else
            defaults
          end
        end
      end # ActionOptions
    end # Publish
  end # Mixins
end # Pancake
