require 'uri/common'

module URI
  module QueryParams
    # RFC 3986 unsafe characters (including ' ')
    UNSAFE = [
      '!', '*', "'", '(', ')', ';', ':', '@', '&', '=', '+', '$', ',',
      '/', '?', '%', '#', '[', ']', ' ', "\f", "\n", "\r", "\t", "\v",
      "\x7f", *("\x00".."\x1f")
    ].join

    #
    # Parses a URI query string.
    #
    # @param [String] query_string
    #   The URI query string.
    #
    # @yield [name,value]
    #   The given block will be passed each parsed query param.
    #
    # @yieldparam [String] name
    #   The name of the query param.
    #
    # @yieldparam [String] value
    #   The value of the query param.
    #
    # @return [Hash{String => String}]
    #   The parsed query parameters.
    #
    # @example
    #   QueryParams.parse("x=1&y=2")
    #   # => {"x"=>"1", "y"=>"2"}
    #
    # @example
    #   QueryParams.parse("x=a%20b%20c&y")
    #   # => {"x"=>"a b c", "y"=>""}
    #
    # @note
    #   Version 0.6.0 allows {parse} to yield the query params, in the order
    #   they are parsed.
    #
    # @api semipublic
    #
    def self.parse(query_string)
      query_params = {}

      if query_string
        query_string.split('&').each do |param|
          # skip empty params
          next if param.empty?

          name, value = param.split('=',2)
          value = if value then URI::DEFAULT_PARSER.unescape(value)
                  else          ''
                  end

          yield(name,value) if block_given?
          query_params[name] = value
        end
      end

      return query_params
    end

    #
    # Escapes a URI query param value.
    #
    # @param [Array, true, false, nil, #to_s] value
    #   The query param value to escape.
    #
    # @return [String]
    #   The raw escaped query param value.
    #
    # @since 0.7.1
    #
    # @api semipublic
    #
    def self.escape(value)
      case value
      when Array      then URI::DEFAULT_PARSER.escape(value.join(' '),UNSAFE)
      when true       then 'active'
      when false, nil then ''
      else                 URI::DEFAULT_PARSER.escape(value.to_s,UNSAFE)
      end
    end

    #
    # Dumps the URI query params.
    #
    # @param [Hash{String => String}] query_params
    #   The query params.
    #
    # @return [String]
    #   The dumped URI query string.
    #
    # @example Dumping Strings
    #   QueryParams.dump('x' => '1', 'y' => '2')
    #   # => "x=1&x=2"
    #
    # @example Dumping non-Strings
    #   QueryParams.dump(:x => 1, :y => true, :z => false)
    #   # => "x=1&x=active&z="
    #
    # @example Dumping Arrays
    #   QueryParams.dump(:x => ['a','b','c'])
    #   # => "x=a%20b%20c"
    #
    # @since 0.5.0
    #
    # @api semipublic
    #
    def self.dump(query_params)
      query_params.map { |name,value|
        "#{name}=#{escape(value)}"
      }.join('&')
    end

  end
end