module Rack::App::Payload::Parser::Builder::Formats
  extend(self)

  JSON_CONTENT_TYPES = [
      'application/json',
      'application/x-javascript',
      'text/javascript',
      'text/x-javascript',
      'text/x-json'
  ].freeze

  JSON_PARSER = proc do |io|
    begin
      ::JSON.load(io)
    rescue ::JSON::ParserError => ex
      rr = Rack::Response.new
      rr.status = 400
      rr.write(ex.message)
      throw(:rack_response, rr)
    end
  end

  def json(builder)
    require 'json'
    JSON_CONTENT_TYPES.each do |content_type|
      builder.on(content_type, &JSON_PARSER)
    end
  end

  JSON_STREAM_CONTENT_TYPES = [
      'application/jsonstream',
      'application/stream+json',
      'application/x-json-stream'
  ].freeze

  JSON_STREAM_PARSER = proc do |io|
    Rack::App::RequestStream.new(io, JSON_PARSER)
  end

  def json_stream(builder)
    JSON_STREAM_CONTENT_TYPES.each do |content_type|
      builder.on(content_type, &JSON_STREAM_PARSER)
    end
  end

  # CSV_CONTENT_TYPE = [
  #   "text/comma-separated-values",
  #   "application/csv",
  #   "text/csv",
  # ]
  #
  # CSV_PARSER = proc do |io|
  #   CSV.parse(io.read)
  # end
  #
  # def csv(builder)
  #   require "csv"
  #   CSV_CONTENT_TYPE.each do |content_type|
  #     builder.on(content_type, &CSV_PARSER)
  #   end
  # rescue LoadError
  # end

  FORM_CONTENT_TYPES = [
      'application/x-www-form-urlencoded',
  # 'multipart/form-data'
  ].freeze

  FORM_SEP_CHAR = '&'.freeze

  RACK_QUERY_PARSER = if Rack::Utils.respond_to?(:default_query_parser)
                        lambda do |form|
                          ::Rack::Utils.default_query_parser.parse_nested_query(form, FORM_SEP_CHAR)
                        end
                      else
                        lambda do |form|
                          ::Rack::Utils.parse_nested_query(form, FORM_SEP_CHAR)
                        end
                      end

  NULL_END_CHAR = /#{"\u0000"}$/

  FORM_PARSER = proc do |io|
    form_vars = io.read
    # Fix for Safari Ajax postings that always append \0
    form_vars.slice!(NULL_END_CHAR)
    RACK_QUERY_PARSER.call(form_vars)
  end

  def www_form_urlencoded(builder)
    FORM_CONTENT_TYPES.each do |content_type|
      builder.on(content_type, &FORM_PARSER)
    end
  end

  alias form www_form_urlencoded
  alias urlencoded www_form_urlencoded

  def accept(builder, *form_names)
    last_name = nil
    form_names.map(&:to_sym).each do |form_name|
      last_name = form_name
      unless respond_to?(form_name)
        raise(NotImplementedError, "unknown formatter: #{last_name}")
      end
      __send__ form_name, builder
    end
  end
end
