module Lotus
  module Action
    # Mime type API
    #
    # @since 0.1.0
    #
    # @see Lotus::Action::Mime::ClassMethods#accept
    module Mime
      # The key that returns accepted mime types from the Rack env
      #
      # @since 0.1.0
      HTTP_ACCEPT          = 'HTTP_ACCEPT'.freeze

      # The header key to set the mime type of the response
      #
      # @since 0.1.0
      CONTENT_TYPE         = 'Content-Type'.freeze

      # The default mime type for an incoming HTTP request
      #
      # @since 0.1.0
      DEFAULT_ACCEPT       = '*/*'.freeze

      # The default mime type that is returned in the response
      #
      # @since 0.1.0
      DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze

      def self.included(base)
        base.class_eval do
          extend ClassMethods
        end
      end

      module ClassMethods
        protected

        # Restrict the access to the specified mime type symbols.
        #
        # @param mime_types[Array<Symbol>] one or more symbols representing mime type(s)
        #
        # @since 0.1.0
        #
        # @example
        #   require 'lotus/controller'
        #
        #   class Show
        #     include Lotus::Action
        #     accept :html, :json
        #
        #     def call(params)
        #       # ...
        #     end
        #   end
        #
        #   # When called with "*/*"              => 200
        #   # When called with "text/html"        => 200
        #   # When called with "application/json" => 200
        #   # When called with "application/xml"  => 406
        def accept(*mime_types)
          mime_types = mime_types.map do |mt|
            ::Rack::Mime.mime_type ".#{ mt }"
          end

          before do
            unless mime_types.find {|mt| accept?(mt) }
              throw 406
            end
          end
        end
      end

      protected
      # Finalize the response by setting the current content type
      #
      # @since 0.1.0
      # @api private
      #
      # @see Lotus::Action#finish
      def finish
        super
        headers.merge! CONTENT_TYPE => content_type
      end

      # Sets the given content type
      #
      # Lotus::Action sets the proper content type automatically, this method
      #   is designed to override that value.
      #
      # @param content_type [String] the content type
      # @return [void]
      #
      # @since 0.1.0
      #
      # @see Lotus::Action::Mime#content_type
      #
      # @example
      #   require 'lotus/controller'
      #
      #   class Show
      #     include Lotus::Action
      #
      #     def call(params)
      #       # ...
      #       self.content_type = 'application/json'
      #     end
      #   end
      def content_type=(content_type)
        @content_type = content_type
      end

      # The content type that will be automatically set in the response.
      #
      # It prefers, in order:
      #   * Explicit set value (see #content_type=)
      #   * Weighted value from Accept
      #   * Default content type
      #
      # To override the value, use <tt>#content_type=</tt>
      #
      # @return [String] the content type from the request.
      #
      # @since 0.1.0
      #
      # @see Lotus::Action::Mime#content_type=
      # @see Lotus::Action::Mime#DEFAULT_CONTENT_TYPE
      #
      # @example
      #   require 'lotus/controller'
      #
      #   class Show
      #     include Lotus::Action
      #
      #     def call(params)
      #       # ...
      #       content_type # => 'text/html'
      #     end
      #   end
      def content_type
        @content_type || accepts || DEFAULT_CONTENT_TYPE
      end

      # Match the given mime type with the Accept header
      #
      # @return [Boolean] true if the given mime type matches Accept
      #
      # @since 0.1.0
      #
      # @example
      #   require 'lotus/controller'
      #
      #   class Show
      #     include Lotus::Action
      #
      #     def call(params)
      #       # ...
      #       # @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'
      #
      #       accept?('text/html')        # => true
      #       accept?('application/xml')  # => true
      #       accept?('application/json') # => false
      #
      #
      #
      #       # @_env['HTTP_ACCEPT'] # => '*/*'
      #
      #       accept?('text/html')        # => true
      #       accept?('application/xml')  # => true
      #       accept?('application/json') # => true
      #     end
      #   end
      def accept?(mime_type)
        !!::Rack::Utils.q_values(accept).find do |mime, _|
          ::Rack::Mime.match?(mime_type, mime)
        end
      end

      private
      def accept
        @accept ||= @_env[HTTP_ACCEPT] || DEFAULT_ACCEPT
      end

      def accepts
        unless accept == DEFAULT_ACCEPT
          ::Rack::Utils.best_q_match(accept, ::Rack::Mime::MIME_TYPES.values)
        end
      end
    end
  end
end