# Copyright (c) 2017 VMware, Inc.  All rights reserved. -- VMware Confidential

import xml.parsers.expat
from translator import AnyTranslator


def _PatchInvalidXML(text):
   """We have invalid XMLs in VCDB. See Bug 1784639."""
   result = text.split(' ')
   result.insert(1, 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
   return ' '.join(result)


class _Logger(object):
   def noop(self, *args, **kwargs):
      pass

   def __getattribute__(self, name):
      return object.__getattribute__(self, 'noop')


class VersionParser(object):
   def __init__(self):
      self.versionId = None
      self.xmlns = None
      self.parser = xml.parsers.expat.ParserCreate()
      self.parser.StartElementHandler = self.Start

   class StopParser(Exception): pass

   def Start(self, name, attrs):
      self.versionId = attrs['versionId']
      self.xmlns = attrs['xmlns']
      raise self.StopParser()

   def Parse(self, text):
      try:
         self.parser.Parse(text)
      except self.StopParser:
         pass


class PatchSequencer(object):
   def __init__(self, ImportSrcPyVmomi, ImportDstPyVmomi, patches=None,
                logger=None):
      self.logger = logger if logger else _Logger()
      self.patches = [] # [Patch]
      self.srcPyVmomi = ImportSrcPyVmomi()
      self.dstPyVmomi = ImportDstPyVmomi()

      # patches - {'branch': {'vmodlNamespace': [Patch]}}
      patches = patches if patches else {}

      # breakingChanges - {'branch': {'vmodlNamespace': count}}
      try:
         breakingChanges = self.srcPyVmomi.VmomiSupport.GetBreakingChanges()
      except:
         breakingChanges = {}

      for branch, patchesPerBranch in patches.items():
         for namespace, patchesPerNamespace in patchesPerBranch.items():
            bcCount = breakingChanges.get(branch, {}).get(namespace, 0)
            for patchesPerSequence in patchesPerNamespace[bcCount:]:
               self.patches.extend(patchesPerSequence)

      numberOfPatches = len(self.patches)

      pyVmomi = [self.srcPyVmomi]
      pyVmomi.extend(ImportSrcPyVmomi() for _ in range(0, numberOfPatches))

      for i in range(1, numberOfPatches + 1):
         for j in range(0, i):
            self.patches[j](pyVmomi[i])

      self.translators = [patch.Translator(pyVmomi[i], pyVmomi[i + 1])
                          for i, patch in enumerate(self.patches)]
      self.translators.append(AnyTranslator(pyVmomi[-1], self.dstPyVmomi))


   def TranslateText(self, text):
      parser = VersionParser()
      parser.Parse(text)
      uri = '%s/%s' % (parser.xmlns, parser.versionId)
      version = self.srcPyVmomi.VmomiSupport.GetVersionFromVersionUri(uri)
      namespace = version.split(".version.", 1)[0]

      try:
         any1 = self.srcPyVmomi.SoapAdapter.Deserialize(text)
      except xml.parsers.expat.ExpatError as origExcept:
         self.logger.debug("Expat Error -> adding missing XSI namespace.")
         try:
            patchedText = _PatchInvalidXML(text)
            any1 = self.srcPyVmomi.SoapAdapter.Deserialize(patchedText)
         except:
            raise Exception('Invalid XML (%s): %s' % (origExcept, text))

      # srcPyVmomi unstable -> srcPyVmomi stable
      srcVersion = self.srcPyVmomi.VmomiSupport.stableVersions.Get(namespace)
      text2 = self.srcPyVmomi.SoapAdapter.Serialize(any1, version=srcVersion)
      any2 = self.srcPyVmomi.SoapAdapter.Deserialize(text2)

      # srcPyVmomi stable -> srcPyVmomi* stable
      for translator in self.translators:
         any2 = translator.TranslateAny(any2)

      # srcPyVmomi* stable -> dstPyVmomi stable
      dstVersion = self.dstPyVmomi.VmomiSupport.stableVersions.Get(namespace)
      result = self.dstPyVmomi.SoapAdapter.Serialize(any2, version=dstVersion)
      return result.decode('utf-8')
