require 'puppet/parser/functions'

Puppet::Parser::Functions.newfunction(:os_transport_url,
                                      :type  => :rvalue,
                                      :arity => 1,
                                      :doc   => <<-EOS
This function builds a os_transport_url string from a hash of parameters.

Valid hash parameteres:
 * transport - (string) type of transport, 'rabbit' or 'amqp'
 * host - (string) single host
 * hosts - (array) array of hosts to use
 * port - (string | integer) port to connect to
 * username - (string) connection username
 * password - (string) connection password
 * virtual_host - (string) virtual host to connect to
 * ssl - (string) is the connection ssl or not ('1' or '0'). overrides the ssl
   key in the query parameter
 * query - (hash) hash of key,value pairs used to create a query string for
   the transport_url.

Only 'transport' and either 'host' or 'hosts' are required keys for the
parameters hash.

The url format that will be generated:
transport://user:pass@host:port[,userN:passN@hostN:portN]/virtual_host?query

NOTE: ipv6 addresses will automatically be bracketed for the URI using the
normalize_ip_for_uri function.

Single Host Example:
os_transport_url({
  'transport'    => 'rabbit',
  'host'         => '1.1.1.1',
  'port'         => '5672',
  'username'     => 'username',
  'password'     => 'password',
  'virtual_host' => 'virtual_host',
  'ssl'          => '1',
  'query'        => { 'key' => 'value' },
})
Generates:
rabbit://username:password@1.1.1.1:5672/virtual_host?key=value&ssl=1

Multiple Hosts Example:
os_transport_url({
  'transport'    => 'rabbit',
  'hosts'        => [ '1.1.1.1', '2.2.2.2' ],
  'port'         => '5672',
  'username'     => 'username',
  'password'     => 'password',
  'virtual_host' => 'virtual_host',
  'query'        => { 'key' => 'value' },
})
Generates:
rabbit://username:password@1.1.1.1:5672,username:password@2.2.2.2:5672/virtual_host?key=value
EOS
) do |arguments|

  require 'uri'

  v = arguments[0]
  klass = v.class

  unless klass == Hash
    raise(Puppet::ParseError, "os_transport_url(): Requires an hash, got #{klass}")
  end

  # type checking for the parameter hash
  v.keys.each do |key|
    v[key] = v[key].to_s if key == 'port'
    klass = (key == 'hosts') ? Array : String
    klass = (key == 'query') ? Hash : klass
    unless (v[key].class == klass) or (v[key] == :undef)
      raise(Puppet::ParseError, "os_transport_url(): #{key} should be a #{klass}")
    end
  end

  # defaults
  parts = {
    :transport => 'rabbit',
    :hostinfo  => 'localhost',
    :path      => '/',
  }

  unless v.include?('transport')
    raise(Puppet::ParseError, 'os_transport_url(): transport is required')
  end

  unless v.include?('host') or v.include?('hosts')
    raise(Puppet::ParseError, 'os_transport_url(): host or hosts is required')
  end

  if v.include?('host') and v.include?('hosts')
    raise(Puppet::ParseError, 'os_transport_url(): cannot use both host and hosts.')
  end

  parts[:transport] = v['transport']

  if v.include?('username') and (v['username'] != :undef) and (v['username'].to_s != '')
    parts[:userinfo] = URI.escape(v['username'])
    if v.include?('password') and (v['password'] != :undef) and (v['password'].to_s != '')
      parts[:userinfo] += ":#{URI.escape(v['password'])}"
    end
  end

  if v.include?('host')
    host = function_normalize_ip_for_uri([v['host']])
    host += ":#{v['port'].to_s}" if v.include?('port')
    if parts.include?(:userinfo)
      parts[:hostinfo] = "#{parts[:userinfo]}@#{host}"
    else
      parts[:hostinfo] = "#{host}"
    end
  end

  if v.include?('hosts')
    hosts = function_normalize_ip_for_uri([v['hosts']])
    # normalize_ip_for_uri may return a string, so check that we still have an
    # array
    hosts = [hosts] if hosts.kind_of?(String)
    hosts = hosts.map{ |h| "#{h}:#{v['port'].to_s}" } if v.include?('port')
    if parts.include?(:userinfo)
      parts[:hostinfo] = hosts.map { |h| "#{parts[:userinfo]}@#{h}" }.join(',')
    else
      parts[:hostinfo] = hosts.join(',')
    end
  end

  parts[:path] = "/#{v['virtual_host']}" if v.include?('virtual_host')

  # support previous ssl option on the function. Setting ssl will
  # override ssl if passed in via the query parameters
  if v.include?('ssl')
    # ssl can be passed in as a query paramter but should be 0/1. See
    # http://docs.celeryproject.org/projects/kombu/en/latest/userguide/connections.html#urls
    # so we rely on the stdlib str2bool and bool2num to ensure it's in the
    # format
    ssl_val = function_bool2num([function_str2bool([v['ssl']])])
    if v.include?('query')
      v['query'].merge!({ 'ssl' => ssl_val })
    else
      v['query'] = { 'ssl' => ssl_val }
    end
  end

  parts[:query] = v['query'].map{ |k,val| "#{k}=#{val}" }.join('&') if v.include?('query')


  url_parts = []
  url_parts << parts[:transport]
  url_parts << '://'
  url_parts << parts[:hostinfo]
  url_parts << parts[:path]
  url_parts << '?' << parts[:query] if parts.include?(:query)
  url_parts.join()
end
