#!/usr/bin/env python
"""
Copyright 2008-2020 VMware, Inc.  All rights reserved. -- VMware Confidential

This module handles dynamic type infomation for PyJack managed objects
"""
__author__ = "VMware, Inc"

from pyVmomi import VmomiSupport
import logging


# Get WSDL Array name
#
# @param  name the non-array wsdlname
# @return WSDL array name
def GetWsdlArrayName(name):
    """Get WSDL array name"""
    if name:
        return "ArrayOf" + name[0].capitalize() + name[1:]
    else:
        return None


# Dynamic type manager
class DynTypeMgr:
    """Dynamic type manager"""

    def __init__(self):
        self._dynTypes = {}

    # Get all dynamic types
    #
    # @return a dict of {vmodl name, dynamic types}
    def GetTypes(self):
        """Get registered dynamic types"""
        return self._dynTypes.copy()

    # Create a data object type
    # Note: Must call RegisterTypes to seal the type
    #
    # @param vmodlName the VMODL name of the type
    # @param wsdlName the WSDL name of the type
    # @param parent the WSDL name of the parent type
    # @param version the version of the type
    # @param props properties of the type
    # @return the new data object type
    def CreateDataType(self, vmodlName, wsdlName, parent, version, props):
        """Create data type"""

        # Cannot add array type directly
        assert (not wsdlName.startswith("ArrayOf"))
        self._FailIfWsdlTypeExists(version, wsdlName)

        # Convert to pyVmomi properties
        properties = self._ConvertDataProperties(props)

        # The types created by this function are currently used at the bootup
        # time of hostd.  So, creating and loading them immediately without
        # deferring the loading of the type Create data type
        aType = VmomiSupport.CreateAndLoadDataType(vmodlName, wsdlName, parent,
                                                   version, properties)

        return aType

    # Create a managed object type
    # Note: Must call RegisterTypes to seal the type
    #
    # @param vmodlName the VMODL name of the type
    # @param wsdlName the WSDL name of the type
    # @param parent the WSDL name of the parent type
    # @param version the version of the type
    # @param props properties of the type
    # @param methods methods of the type
    # @return the new managed object type
    def CreateManagedType(self, vmodlName, wsdlName, parent, version, props,
                          methods):
        """Create managed type"""

        # Cannot add array type directly
        assert (not wsdlName.startswith("ArrayOf"))
        self._FailIfWsdlTypeExists(version, wsdlName)
        # Convert to pyVmomi methods / properties
        allMethods = self._ConvertMethods(methods)
        properties = self._ConvertManagedProperties(props)

        # The types created by this function are currently used at the bootup
        # time of hostd.  So, creating and loading them immediately without
        # deferring the loading of the type Create managed type
        aType = VmomiSupport.CreateAndLoadManagedType(vmodlName, wsdlName,
                                                      parent, version,
                                                      properties, allMethods)

        return aType

    # Create an enum type
    # Note: Must call RegisterTypes to seal the type
    #
    # @param vmodlName the VMODL name of the type
    # @param wsdlName the WSDL name of the type
    # @param version the version of the type
    # @param values enum values
    # @return the new enum type
    def CreateEnumType(self, vmodlName, wsdlName, version, values):
        """Create enum type"""

        # Cannot add array type directly
        assert (not wsdlName.startswith("ArrayOf"))
        self._FailIfWsdlTypeExists(version, wsdlName)
        # The types created by this function are currently used at the bootup
        # time of hostd.  So, creating and loading them immediately without
        # deferring the loading of the type
        aType = VmomiSupport.CreateAndLoadEnumType(vmodlName, wsdlName,
                                                   version, values)
        return aType

    # Register type
    #
    # @param  aType type returns from CreateXYZType
    def RegisterType(self, aType):
        """Register type"""
        self._RegisterTypeAndArrayType(aType)

    # Register types
    #
    # @param  types iteratable types returns from CreateXYZType
    def RegisterTypes(self, types):
        """Register types"""
        for aType in types:
            self.RegisterType(aType)

    # Lookup wsdl type. Fail if type exists
    #
    # @param wsdlName the WSDL name of the type
    # @throw KeyError if type exists
    def _FailIfWsdlTypeExists(self, version, wsdlName):
        """Lookup wsdl type. Raise KeyError if type exists"""
        try:
            ns = VmomiSupport.GetWsdlNamespace(version)
            aType = VmomiSupport.GetWsdlType(ns, wsdlName)
        except KeyError:
            aType = None

        if aType:
            message = "Type '" + wsdlName + "' already exists"
            logging.error(message)
            raise KeyError(message)

    # Register type
    #
    # @param name the vmodl name of the type
    # @param aType type
    def _RegisterType(self, name, aType):
        """Register type"""
        dynType = self._dynTypes.get(name)
        if dynType:
            message = "Type '" + name + "' already exists"
            logging.error(message)
            raise KeyError(message)
        # Register type
        self._dynTypes[name] = aType

    # Register both the type and the array type
    #
    # @param aType the named type
    def _RegisterTypeAndArrayType(self, aType):
        """Register both the type and the array type"""
        vmodlName = VmomiSupport.GetVmodlName(aType)
        arrayTypeVmodlName = vmodlName + "[]"
        arrayType = VmomiSupport.GetVmodlType(arrayTypeVmodlName)
        self._RegisterType(vmodlName, aType)
        self._RegisterType(arrayTypeVmodlName, arrayType)

    # Convert properties to pyVmomi managed object properties
    #
    # @param  props List of properties with the following attrs:
    #           name, wsdlType, version, flags
    # @return pyVmomi properties
    @staticmethod
    def _ConvertManagedProperties(props):
        """Convert properties to pyVmomi properties"""
        return [(prop.name, prop.type, prop.version, prop.flags, prop.privId) \
                                                  for prop in props]

    # Convert properties to pyVmomi data object properties
    #
    # @param  props List of properties with the following attrs:
    #           name, wsdlType, version, flags
    # @return pyVmomi properties
    @staticmethod
    def _ConvertDataProperties(props):
        """Convert properties to pyVmomi properties"""
        return [(prop.name, prop.type, prop.version, prop.flags) \
                                                  for prop in props]

    # Convert methods to pyVmomi methods
    #
    # @param  allMethods List of method with the following attrs:
    #           name, wsdlName, version, params, returns
    #         where params is a list of obj with attrs:
    #           name, wsdlType, version, flags
    #         and returns has attr: wsdlType
    # @return pyVmomi methods
    @staticmethod
    def _ConvertMethods(allMethods):
        """Convert methods to pyVmomi methods"""
        methods = []
        for method in allMethods:
            # Handle method parameters
            methodParams = \
                     [(param.name, param.type, param.version, param.flags,
                       param.privId) \
                      for param in method.params]
            if method.isTask:
                taskReturnType = "vim.Task"
            else:
                taskReturnType = method.returns.type

            aMethod = (method.name, method.wsdlName, method.version,
                       tuple(methodParams),
                       (method.returns.flags, taskReturnType,
                        method.returns.type), method.privId, method.faults)
            methods.append(aMethod)
        return methods


# Get dynamic type manager
_gDynTypeMgr = DynTypeMgr()


def GetDynTypeMgr():
    """Get the dynamic type manager"""
    return _gDynTypeMgr
