# Early, and incomplete implementation of -04.
#
import re
import urllib.request, urllib.parse, urllib.error

RESERVED = ":/?#[]@!$&'()*+,;="
OPERATOR = "+./;?|!@"
EXPLODE = "*+"
MODIFIER = ":^"
TEMPLATE = re.compile(r"{(?P<operator>[\+\./;\?|!@])?(?P<varlist>[^}]+)}", re.UNICODE)
VAR = re.compile(r"^(?P<varname>[^=\+\*:\^]+)((?P<explode>[\+\*])|(?P<partial>[:\^]-?[0-9]+))?(=(?P<default>.*))?$", re.UNICODE)

def _tostring(varname, value, explode, operator, safe=""):
  if type(value) == type([]):
    if explode == "+":
      return ",".join([varname + "." + urllib.parse.quote(x, safe) for x in value])
    else:
      return ",".join([urllib.parse.quote(x, safe) for x in value])
  if type(value) == type({}):
    keys = list(value.keys())
    keys.sort()
    if explode == "+":
      return ",".join([varname + "." + urllib.parse.quote(key, safe) + "," + urllib.parse.quote(value[key], safe) for key in keys])
    else:
      return ",".join([urllib.parse.quote(key, safe) + "," + urllib.parse.quote(value[key], safe) for key in keys])
  else:
    return urllib.parse.quote(value, safe)


def _tostring_path(varname, value, explode, operator, safe=""):
  joiner = operator
  if type(value) == type([]):
    if explode == "+":
      return joiner.join([varname + "." + urllib.parse.quote(x, safe) for x in value])
    elif explode == "*":
      return joiner.join([urllib.parse.quote(x, safe) for x in value])
    else:
      return ",".join([urllib.parse.quote(x, safe) for x in value])
  elif type(value) == type({}):
    keys = list(value.keys())
    keys.sort()
    if explode == "+":
      return joiner.join([varname + "." + urllib.parse.quote(key, safe) + joiner + urllib.parse.quote(value[key], safe) for key in keys])
    elif explode == "*":
      return joiner.join([urllib.parse.quote(key, safe) + joiner + urllib.parse.quote(value[key], safe) for key in keys])
    else:
      return ",".join([urllib.parse.quote(key, safe) + "," + urllib.parse.quote(value[key], safe) for key in keys])
  else:
    if value:
      return urllib.parse.quote(value, safe)
    else:
      return ""

def _tostring_query(varname, value, explode, operator, safe=""):
  joiner = operator
  varprefix = ""
  if operator == "?":
    joiner = "&"
    varprefix = varname + "="
  if type(value) == type([]):
    if 0 == len(value):
      return ""
    if explode == "+":
      return joiner.join([varname + "=" + urllib.parse.quote(x, safe) for x in value])
    elif explode == "*":
      return joiner.join([urllib.parse.quote(x, safe) for x in value])
    else:
      return varprefix + ",".join([urllib.parse.quote(x, safe) for x in value])
  elif type(value) == type({}):
    if 0 == len(value):
      return ""
    keys = list(value.keys())
    keys.sort()
    if explode == "+":
      return joiner.join([varname + "." + urllib.parse.quote(key, safe) + "=" + urllib.parse.quote(value[key], safe) for key in keys])
    elif explode == "*":
      return joiner.join([urllib.parse.quote(key, safe) + "=" + urllib.parse.quote(value[key], safe) for key in keys])
    else:
      return varprefix + ",".join([urllib.parse.quote(key, safe) + "," + urllib.parse.quote(value[key], safe) for key in keys])
  else:
    if value:
      return varname + "=" + urllib.parse.quote(value, safe)
    else:
      return varname 

TOSTRING = {
    "" : _tostring,
    "+": _tostring,
    ";": _tostring_query,
    "?": _tostring_query,
    "/": _tostring_path,
    ".": _tostring_path,
    }


def expand(template, vars):
  def _sub(match):
    groupdict = match.groupdict()
    operator = groupdict.get('operator')
    if operator is None:
      operator = ''
    varlist = groupdict.get('varlist')

    safe = "@"
    if operator == '+':
      safe = RESERVED
    varspecs = varlist.split(",")
    varnames = []
    defaults = {}
    for varspec in varspecs:
      m = VAR.search(varspec)
      groupdict = m.groupdict()
      varname = groupdict.get('varname')
      explode = groupdict.get('explode')
      partial = groupdict.get('partial')
      default = groupdict.get('default')
      if default:
        defaults[varname] = default
      varnames.append((varname, explode, partial))

    retval = []
    joiner = operator
    prefix = operator
    if operator == "+":
      prefix = ""
      joiner = ","
    if operator == "?":
      joiner = "&"
    if operator == "":
      joiner = ","
    for varname, explode, partial in varnames:
      if varname in vars:
        value = vars[varname]
        #if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults:
        if not value and value != "" and varname in defaults:
          value = defaults[varname]
      elif varname in defaults:
        value = defaults[varname]
      else:
        continue
      retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe))
    if "".join(retval):
      return prefix + joiner.join(retval)
    else:
      return ""

  return TEMPLATE.sub(_sub, template)
