require File.join(File.dirname(__FILE__), "publish", "action_options")
module Pancake
  module Mixins
    module Publish

      def self.extended(base)
        base.class_eval do
          extlib_inheritable_accessor :actions, :formats
          self.actions = {}
          self.formats = [:html]
        end
      end

      # Accepts a list of symbols representing the formats each action in the
      # controller will return. By default the list consists only of :html
      #
      #   provides :html, :xml
      #
      # @api public
      def provides(*args)
        self.formats = args
      end

      # Causes the next method added immediately after it’s call to be defined
      # as an action. It also generates an instance of ActionOptions, which
      # encapsulates all the parameters the action accepts/expects and also
      # the formats that it can potentially return.
      #
      #   publish :id => as_integer(:req)
      #   def show
      #     ...
      #   end
      #
      # The example above publishes the action "show" and configures the
      # following options for it:
      #
      # - The parameter 'id' will be coerced into an Integer
      # - It also must not be blank, it is required.
      #
      # The publish declaration can also create much more sophisticated
      # constraints. You can declare a parameter as optional, give it a default
      # value if it is missing.
      #
      # Here, 'start' and 'end' are required — the default — and are coerced into
      # date values. The 'page' parameter is optional, with the default being 1.
      #
      #   publish :start => as_date, :end => as_date :page => as_integer(1)
      #
      # For a list of the built in coercions look in the API for methods prefixed
      # with 'as_'. These are intended to be used in your publish declarations.
      #
      # #publish can also handle the specification of formats for an action. It
      # accepts an additional two options, :provides and :only_provides.
      #
      # To the list of globally declared formats, :provides adds additional
      # formats to an action.
      #
      #   publish :provides => :xml
      #
      # The :only_provides option overrides any globally declared formats for an
      # action.
      #
      #   publish :only_provides => [:json, :xml]
      #
      # @api public
      def publish(opts = {})
        @pending_publication_opts = opts
      end

      # Used when declaring configuration for parameters in the publish
      # declaration. It returns an array of the type, default value an
      # additional options.
      #
      # @param type [Object] The
      #
      # The current supported types are
      # - :integer
      # - :date
      # - :string
      #
      # For details on the options supported by each type, please see the
      # corresponding methods declared in the ActionOptions class. These methods
      # are named as the type, prefixed with 'validate_and_coerce_'
      #
      #
      # @example
      #   Here is an example where we declare an integer and that it is required
      #
      #     as(:integer, :req)
      #
      #   Or that it is optional, but has a default value if it is missing.
      #
      #     as(:integer, 21)
      #
      # @api public
      def as(type, default = :req, opts = {})
        [type, default, opts]
      end

      # Takes a parameters hash, and validates each entry against the options
      # defined for this action. It will flag required params when missing,
      # insert defaults or coerce values into the desired type. It mutates
      # the params hash itself.
      #
      # @api private
      def validate_and_coerce_params(action, params)
        actions[action].validate_and_coerce(params)
      end


      # This hook is used in conjunction with the #publish method to expose
      # instance methods as actions. Obviously, it should never be called
      # directly :)
      #
      # @api private
      def method_added(name)
        super
        if @pending_publication_opts
          self.actions[name.to_s] = ActionOptions.new(formats, @pending_publication_opts)
          @pending_publication_opts = nil
        end
      end
    end # Publish
  end # Mixins
end # Pancake
