#!/usr/bin/python
import sys
import xml.dom.minidom
import re
import pprint
import missing_info

skip_list = set(['version', 'help'])

# table_type is a synonym to storage_engine
OPTIONS_TO_IGNORE = ["default-table-type", "new"]

doc = xml.dom.minidom.parse('mysqld.xml')

opts = []

bool_tuples = (('off', 'on'), ('no','yes'), ('0','1'), ('false','true'))
bool_dict = {}
for tup in bool_tuples:
  bool_dict[tup[0]] = tup
  bool_dict[tup[1]] = tup

tags = {}
types = {}


def count_tag(dic, tag):
  if tag in dic:
    dic[tag] += 1
  else:
    dic[tag] = 1

#-------------------------------------------------------------------------------
def parse_boolean(node, value, opt):
  disabledby = None
  if 'disabledby' in opt:
    disabledby = opt['disabledby']
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE:
      if t.tagName == 'value':
        for (aname, aval) in t.attributes.items():
          if aname == 'default':
            if aname in value:
              print "existing value tag", aname, value
            else:
              value[str(aname)] = str(aval.lower())
          else:
            print "Uknown attr in value", aname, opt['name']
            quit()
      elif t.tagName == 'choice':
        for (aname, aval) in t.attributes.items():
          if aname == 'value':
            if 'choice' in value:
              value['choice'].append(str(aval.lower()))
            else:
              value['choice'] = [str(aval.lower())]
          else:
            print "Uknown attr in choice", aname, opt['name']
            quit()
      else:
        print "Warning unkown tag in boolean value", aname, aval
        quit()

  default = None
  if 'default' in value:
    default = value['default'].lower()

  switch = bool_dict['0'] # pick 0|1 as a default assignable items

  if default in bool_dict:
    switch = bool_dict[default]

  if disabledby is None:
    value['off'] = switch[0]
  else:
    value['off'] = 'disabledby'

  value['on'] = switch[1]

#-------------------------------------------------------------------------------
def parse_set(node, value, opt):
  parse_enumeration(node, value, opt)
  value['type'] = 'set'

#-------------------------------------------------------------------------------
def parse_string(node, value, opt):
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE:
      if t.tagName == 'value':
        for (aname, aval) in t.attributes.items():
          if aname in value:
            print "existing value tag", aname, value
          else:
            value[str(aname)] = str(aval)
      elif t.tagName == 'choice':
        for (aname, aval) in t.attributes.items():
          if aname == 'value':
            if 'choice' in value:
              value['choice'].append(str(aval))
            else:
              value['choice'] = [str(aval)]
          else:
            print "Uknown attr in choice", aname, opt['name']
            quit()
      else:
        print "Warning unkown tag in boolean value", aname, aval
        quit()

#-------------------------------------------------------------------------------
def parse_numeric(node, value, opt):
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE:
      if t.tagName == 'value':
        for (aname, aval) in t.attributes.items():
          if aname in value:
            print "existing value tag", aname, value
          else:
            value[str(aname)] = str(aval)
      elif t.tagName == 'choice':
        for (aname, aval) in t.attributes.items():
          if aname == 'value':
            if 'choice' in value:
              value['choice'].append(str(aval))
            else:
              value['choice'] = [str(aval)]
          else:
            print "Uknown attr in choice", aname, opt['name']
            quit()
      else:
        print "Warning unkown tag in boolean value", aname, aval
        quit()

#-------------------------------------------------------------------------------
def parse_filename(node, value, opt):
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE:
      if t.tagName == 'value':
        for (aname, aval) in t.attributes.items():
          value[str(aname)] = str(aval)

#-------------------------------------------------------------------------------
def parse_enumeration(node, value, opt):
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE:
      if t.tagName == 'value':
        for (aname, aval) in t.attributes.items():
          if aname == 'default':
            if aname in value:
              print "existing value tag", aname, value
            else:
              value[str(aname)] = str(aval)
          else:
            print "Uknown attr in value", aname, opt['name']
            quit()
      elif t.tagName == 'choice':
        for (aname, aval) in t.attributes.items():
          if aname == 'value':
            if 'choice' in value:
              value['choice'].append(str(aval))
            else:
              value['choice'] = [str(aval)]
          else:
            print "Uknown attr in choice", aname, opt['name']
            quit()
      else:
        print "Warning unkown tag in boolean value", aname, aval
        quit()
  value['type'] = 'enum'

#-------------------------------------------------------------------------------
def parse_bitmap(node, value, opt):
  pass

#-------------------------------------------------------------------------------
type_parsers = {'boolean':parse_boolean
               ,'set':parse_set
               ,'string':parse_string
               ,'numeric':parse_numeric
               ,'filename':parse_filename
               ,'dirname':parse_filename
               ,'enumeration':parse_enumeration
               ,'bitmap':parse_bitmap
               }

#-------------------------------------------------------------------------------
def parse_version_str(version_str):
  version = None
  try:
    res = re.match("([0-9]+)\.([0-9]+)\.([0-9]+)|([0-9]+)\.([0-9]+)", version_str)
    if res:
      tokens = res.groups()
      if tokens[0] is not None:
        version = (int(tokens[0]), int(tokens[1]), int(tokens[2]))
      else:
        version = (int(tokens[3]), int(tokens[4]))
  except ValueError, e:
    print "ERROR! incorrect version attribute value '" + version_str + "', ", type(version_str)

  return version

#-------------------------------------------------------------------------------
def parse_values(node, opt):
  value = {}
  for n,v in node.attributes.items():
    value[str(n)] = str(v)

  vartype = str(value['vartype'])

  value['type'] = vartype
  del value['vartype']

  type_parsers[vartype](node, value, opt)

  if 'platform' in value:
    if value['platform'] == 'all':
      del value['platform']

  if 'inversion' in value:
      value['inversion'] = parse_version_str(value['inversion'])
  if 'outversion' in value:
      value['outversion'] = parse_version_str(value['outversion'])

  return value

#-------------------------------------------------------------------------------
def guess_values(opt):
  name = opt['name']
  values = opt['values']
  if len(values) == 0:
    if name[:4] == 'skip':
      values.append({'off':'del', 'on':'name', 'type':'boolean'})

#-------------------------------------------------------------------------------
def parse_optype(node, option):
  optype = None
  if node.getAttribute('class') == 'mycnf':
    optype = {}
    for i in node.attributes.items():
      if i[0] != 'class':
        optype[str(i[0])] = str(i[1])

  # make cleanup
  if optype:
    if 'setvar' in optype:
      del optype['setvar']
    if 'format' in optype:
      if optype['format'] == option['name']:
        del optype['format']
  return optype

#-------------------------------------------------------------------------------
def parse_version_str(version_str):
  version = None
  try:
    res = re.match("([0-9]+)\.([0-9]+)\.([0-9]+)|([0-9]+)\.([0-9]+)", version_str)
    if res:
      tokens = res.groups()
      if tokens[0] is not None:
        version = (int(tokens[0]), int(tokens[1]), int(tokens[2]))
      else:
        version = (int(tokens[3]), int(tokens[4]))
  except ValueError, e:
    print "ERROR! incorrect version attribute value '" + version_str + "', ", type(version_str)
  return version

#-------------------------------------------------------------------------------
def ver_cmp(v1, v2):
  minlen = min(len(v1), len(v2))
  return cmp(v1[0:minlen], v2[0:minlen])

#-------------------------------------------------------------------------------
def ver_diff(v1,v2):
  return abs(reduce(lambda x,y: x*10 + y, v1) - reduce(lambda x,y: x*10 + y, v2))

#-------------------------------------------------------------------------------
def parse_versions(versions_node, option):
  vers = []
  removed = []
  introduced = []
  for node in versions_node.childNodes:
    if xml.dom.Node.ELEMENT_NODE == node.nodeType:
      if node.tagName == "manual":
        version_str = node.getAttribute('version')
        version = parse_version_str(version_str)
        if version:
          if len(version) > 2:
            print "Error: manual version is set in x.y.z format", option
            quit()
          vers.append(version)
      elif node.tagName == 'introduced':
        version_str = node.getAttribute('version')
        version = parse_version_str(version_str)
        if version:
          introduced.append(version)
      elif node.tagName == "removed":
        version_str = node.getAttribute('version')
        version = parse_version_str(version_str)
        if version:
          removed.append(version)

  vers.sort()
  vers.append((0,0))
  res = []
  skip = False
  for i in range(0, len(vers)-1):
    if vers[i][0:2] == vers[i+1][0:2]:
      res.append(max(vers[i], vers[i+1]))
      skip = True
    else:
      if not skip:
        res.append(vers[i])
      else:
        skip = False

  return [tuple(introduced), tuple(res), tuple(removed)]

#-------------------------------------------------------------------------------
def parse_types(node, option):
  optype = None
  for t in node.childNodes:
    if t.nodeType == t.ELEMENT_NODE and t.tagName == 'optype':
      cur_optype = parse_optype(t, option)
      if optype is None and cur_optype is not None:
        optype = cur_optype
  return optype

#-------------------------------------------------------------------------------
for option in doc.documentElement.getElementsByTagName('mysqloption'):
  opt = {}
  optid = str(option.getAttribute('id'))
  opt['name'] = optid
  opt['caption'] = optid
  is_mycnf_opt = False
  opt['values'] = []

  for t in option.getElementsByTagName('types'):
    otype = parse_types(t, opt)
    if otype is not None:
      is_mycnf_opt = True
      if len(otype) > 0:
        opt['optype'] = otype

  for t in option.getElementsByTagName('disabledby'):
    items = t.attributes.items()
    # items usually look like [('xref', 'skip-merge')] for optid 'merge'
    if len(items) == 1:
      items = items[0]
      opt['disabledby'] = str(items[1])
    else:
      print "Warning: disabledby has more that one value or no values", optid, items

  if is_mycnf_opt:
    for node in option.childNodes:
      if node.nodeType == node.ELEMENT_NODE:
        if node.tagName == 'values':
          ovalue = parse_values(node, opt)
          opt['values'].append(ovalue)
        elif node.tagName == 'versions':
          opt['versions'] = parse_versions(node, opt)
        elif node.tagName == 'shortdescription':
          firstChild = node.firstChild
          desc = firstChild.data.encode('ascii').replace("\n", "") if firstChild else ""
          d1 = desc
          d2 = desc.replace('  ', ' ')
          while d1 != d2:
            d1 = d2
            d2 = d2.replace('  ', ' ')
          desc = d2
          opt['description'] = desc.strip(" ")
          #if len(desc) > 80:
          #  f = open('long_desc.txt','a')
          #  f.write("option '" + optid + "' has long desc. " + desc + "\n")
          #  f.close()
        elif node.tagName == 'deprecated':
          version = node.getAttribute('version')
          opt['deprecated'] = parse_version_str(version)

    if optid[:5] != 'maria' and optid[:6] != 'falcon' and optid[:3] != 'bdb' and (not optid in skip_list):
        opts.append(opt)

    if 'values' in opt and len(opt['values']) == 0:
      guess_values(opt)

#-------------------------------------------------------------------------------
# Merge from missing_info
for o in opts:
  missing_def = missing_info.get_def(o['name'])
  if missing_def:
    for k,v in missing_def.iteritems():
      o[k] = v
    print "Merged", missing_def
    print "    to", o
    print "----"

for o in missing_info.non_requested:
  missing_def = missing_info.get_def(o)
  opts.append(missing_def)

#-------------------------------------------------------------------------------
# make list const
for o in opts:
  if 'values' in o and len(o['values']) > 0:
    o['values'] = values = list(o['values'])
for o in opts:
  if len(o['values']) == 0:
    print "Warning: option", o['name'], "has no values"

f = open('raw_opts.py', 'w')
f.write("ropts = ")
pp = pprint.PrettyPrinter(indent=2, stream=f)
pp.pprint(opts)
f.close()
