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

"""VCDB VMODL In-Place Upgrade (B2B) Translator

This module defines a mechanism to translate VMOMI objects from one pyVmomi
instance to another. The main class, AnyTranslator, is used in the class
PatchSequencer where the sequencing of (breaking) changes is handled.

The way to use this module is to instantiate an AnyTranslator object and let it
do the translation. The translation itself is distributed across both the
AnyTranslator and _AnyTranslator classes, the former provides the primitives,
the latter provides the actual Any traversal.
"""


def _Identity(x):
    """An identity transformation function to be used in a translation map"""
    return x


class BreakingChangeException(Exception):
    """The translator assumes that there are no breaking changes.

    Throw this exception if a breaking change between source and
    destination pyVmomi is detected.
    """
    pass


class _AnyTranslator(object):
    """Translator implementation for Any

    The translation is done by induction on the definition of the source Any.
    Note the 'base' translator which provides the translation of primitives.
    """

    def __init__(self, base):
        """Take everything necessary from the 'base' translator"""

        self.base = base
        self.srcPyVmomi = self.base.srcPyVmomi
        self.dstPyVmomi = self.base.dstPyVmomi
        self.stack = []

    def Translate(self, anyObj):
        """Main translator API"""

        result = self._TranslateAny(anyObj, type(anyObj))

        while self.stack:
            srcTop, dstTop = self.stack.pop()
            self._TranslateDataObjectProperties(srcTop, dstTop)

        return result

    def _TranslateAny(self, anyObj, srcType):
        """Implementation for Any"""

        # any
        if srcType == object:
            srcType = type(anyObj)

        dstType = self.base.TRANSLATIONS.get(srcType)

        # primitive
        if dstType:
            if issubclass(srcType, self.srcPyVmomi.VmomiSupport.Array):
                dstItemType = self.base.TRANSLATIONS[srcType.Item]
                return dstType(dstItemType(item) for item in anyObj)
            else:
                return dstType(anyObj)
        # enum
        elif issubclass(srcType, self.srcPyVmomi.VmomiSupport.Enum):
            dstType = self.base.TranslateType(srcType)
            return dstType(anyObj)
        # managed object reference
        elif issubclass(srcType, self.srcPyVmomi.types.ManagedObject):
            return self.base.TranslateManagedObject(anyObj)
        # data object
        elif issubclass(srcType, self.srcPyVmomi.types.DataObject):
            return self._TranslateDataObject(anyObj)
        # any array
        elif issubclass(srcType, self.srcPyVmomi.types.ObjectArray):
            dstType = self.dstPyVmomi.types.ObjectArray
            return dstType(self._TranslateAny(item, type(item))
                           for item in anyObj)
        # array
        elif issubclass(srcType, self.srcPyVmomi.VmomiSupport.Array):
            dstType = self.base.TranslateType(srcType.Item).Array
            # enum
            if issubclass(srcType.Item, self.srcPyVmomi.VmomiSupport.Enum):
                return dstType(dstType.Item(item) for item in anyObj)
            # managed object reference
            elif issubclass(srcType.Item, self.srcPyVmomi.types.ManagedObject):
                return dstType(self.base.TranslateManagedObject(item)
                               for item in anyObj)
            # data object
            elif issubclass(srcType.Item, self.srcPyVmomi.types.DataObject):
                return dstType(self._TranslateDataObject(item)
                               for item in anyObj)

        raise Exception()  # bug

    def _TranslateDataObject(self, dobj):
        """Implementation for DataObject"""

        srcType = type(dobj)
        dstType = self.base.TranslateType(srcType)
        result = dstType()
        self.stack.append((dobj, result))
        return result

    def _TranslateDataObjectProperties(self, srcObj, dstObj):
        """Implementation for the members of a DataObject"""

        # pylint: disable=protected-access
        processedProperties = self.base.TranslateDataObject(srcObj, dstObj)
        processedSrcProperties, processedDstProperties = processedProperties

        srcProperties = {p.name: p
                         for p in srcObj._GetPropertyList()
                         if p.name not in processedSrcProperties}
        dstProperties = {p.name: p
                         for p in dstObj._GetPropertyList()
                         if p.name not in processedDstProperties}

        for propertyName, srcProperty in srcProperties.items():
            dstProperty = dstProperties.pop(propertyName, None)
            srcValue = getattr(srcObj, propertyName)

            if srcValue is None:
                continue  # unset

            if dstProperty.type != self.base.TranslateType(srcProperty.type):
                raise BreakingChangeException()

            setattr(dstObj, propertyName,
                    self._TranslateAny(srcValue, srcProperty.type))

        for propertyName, dstProperty in dstProperties.items():
            if not dstProperty.flags & self.dstPyVmomi.VmomiSupport.F_OPTIONAL:
                raise BreakingChangeException()


class AnyTranslator(object):
    """The main user-facing class for the translation

    This class provides the translation of primitive elements and delegates
    the traversal of a complex pyVmomi object to the _AnyTranslator class.

    On the user-facing side, this class provides an API to translate anything
    that comes from pyVmomi.
    """

    def __init__(self, srcPyVmomi, dstPyVmomi):
        """Initialize the primitive translations"""

        self.srcPyVmomi = srcPyVmomi
        self.dstPyVmomi = dstPyVmomi

        srcTypes = srcPyVmomi.VmomiSupport.types
        dstTypes = dstPyVmomi.VmomiSupport.types

        srcTextType = srcPyVmomi.VmomiSupport.six.text_type
        dstTextType = dstPyVmomi.VmomiSupport.six.text_type

        self.TRANSLATIONS = {
            type(None): _Identity,

            srcTypes.bool: dstTypes.bool,
            srcTypes.BoolArray: dstTypes.BoolArray,

            srcTypes.byte: dstTypes.byte,
            srcTypes.ByteArray: dstTypes.ByteArray,

            srcTypes.short: dstTypes.short,
            srcTypes.ShortArray: dstTypes.ShortArray,

            srcTypes.int: _Identity,  # srcTypes.int == dstTypes.int
            srcTypes.IntArray: dstTypes.IntArray,

            srcTypes.long: dstTypes.long,
            srcTypes.LongArray: dstTypes.LongArray,

            srcTypes.float: _Identity,  # srcTypes.float == dstTypes.float
            srcTypes.FloatArray: dstTypes.FloatArray,

            srcTypes.double: dstTypes.double,
            srcTypes.DoubleArray: dstTypes.DoubleArray,

            srcTypes.str: _Identity,  # srcTypes.str == dstTypes.str
            srcTypes.StrArray: dstTypes.StrArray,

            srcTextType: dstTextType,  # PR 1904488

            srcTypes.URI: dstTypes.URI,
            srcTypes.URIArray: dstTypes.URIArray,

            srcTypes.binary: dstTypes.binary,
            srcTypes.BinaryArray: dstTypes.BinaryArray,

            # srcTypes.datetime == dstTypes.datetime
            srcTypes.datetime: _Identity,
            srcTypes.DatetimeArray: dstTypes.DatetimeArray,

            srcTypes.Link: dstTypes.Link,
            srcTypes.LinkArray: dstTypes.LinkArray,

            srcTypes.type: self.TranslateType,
            srcTypes.TypeArray: dstTypes.TypeArray,
            # data objects
            srcPyVmomi.VmomiSupport.LazyType: self.TranslateType,

            srcTypes.ManagedMethod: self.TranslateManagedMethod,
            srcTypes.ManagedMethodArray: dstTypes.ManagedMethodArray,

            srcTypes.PropertyPath: dstTypes.PropertyPath,
            srcTypes.PropertyPathArray: dstTypes.PropertyPathArray,
        }

    def TranslateAny(self, anyObj):
        """API for Any"""

        return _AnyTranslator(self).Translate(anyObj)

    def TranslateManagedMethod(self, mm):
        """API for ManagedMethod"""

        ns = self.srcPyVmomi.VmomiSupport.GetWsdlNamespace(mm.info.version)
        return self.dstPyVmomi.VmomiSupport.GetWsdlMethod(ns, mm.info.wsdlName)

    def TranslateManagedObject(self, mo):
        """API for ManagedObject"""

        # pylint: disable=protected-access
        moType = self.TranslateType(type(mo))
        return moType(moId=mo._moId, serverGuid=mo._serverGuid)

    def TranslateType(self, t):
        """API for Type"""

        name = self.srcPyVmomi.VmomiSupport.GetVmodlName(t)
        return self.dstPyVmomi.VmomiSupport.GetVmodlType(name)

    def TranslateDataObject(self, srcObj, dstObj):
        """API for DataObject"""

        # pylint: disable=unused-argument
        return [], []
