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

This module is a converter from dynamic type to pyVmomi type
"""
__author__ = "VMware, Inc"

from . import VmomiSupport, vmodl
from .Cache import Cache


# Dynamic type importer
class DynamicTypeImporter:
    """ Dynamic type importer  """

    # Constructor
    #
    # @param  stub Server stub
    def __init__(self, stub, hostSystem=None):
        self.stub = stub
        self.hostSystem = hostSystem

    # Get dynamic type manager
    #
    # @return moRef to dynamic type manager
    @Cache
    def GetTypeManager(self):
        """ Get dynamic type manager """
        dynTypeMgr = None
        if self.hostSystem:
            try:
                dynTypeMgr = self.hostSystem.RetrieveDynamicTypeManager()
            except vmodl.fault.MethodNotFound:
                pass

        if not dynTypeMgr:
            # Older host not support RetrieveDynamicTypeManager
            cmdlineTypesMoId = "ha-dynamic-type-manager"
            dynTypeMgr = vmodl.reflect.DynamicTypeManager(
                cmdlineTypesMoId, self.stub)
        return dynTypeMgr

    # Import dynamic types
    #
    # @param  prefix Only types with the specified prefix are imported
    # @return dynamic types
    @Cache
    def ImportTypes(self, prefix=''):
        """ Build dynamic types """
        # Use QueryTypeInfo to get all types
        dynTypeMgr = self.GetTypeManager()
        filterSpec = None
        if prefix != '':
            filterSpec = vmodl.reflect.DynamicTypeManager.TypeFilterSpec(
                typeSubstr=prefix)
        allTypes = dynTypeMgr.QueryTypeInfo(filterSpec)

        # Convert dynamic types to pyVmomi types
        #
        DynamicTypeConstructor().CreateTypes(allTypes)
        return allTypes


# Construct pyVmomi types from dynamic types definition
class DynamicTypeConstructor:
    """ Dynamic type constructor  """

    _mapFlags = {
        "optional": VmomiSupport.F_OPTIONAL,
        "linkable": VmomiSupport.F_LINKABLE,
        "link": VmomiSupport.F_LINK,
        "secret": VmomiSupport.F_SECRET
    }

    # Constructor
    def __init__(self):
        """ Constructor """
        pass

    # Create pyVmomi types from vmodl.reflect.DynamicTypeManager.AllTypeInfo
    #
    # @param  allTypes vmodl.reflect.DynamicTypeManager.AllTypeInfo
    def CreateTypes(self, allTypes):
        """ Create pyVmomi types from
        vmodl.reflect.DynamicTypeManager.AllTypeInfo
        """
        enumTypes, dataTypes, managedTypes = self._ConvertAllTypes(allTypes)
        self._CreateAllTypes(enumTypes, dataTypes, managedTypes)

    # Convert all dynamic types to pyVmomi type definitions
    #
    # @param  allTypes vmodl.reflect.DynamicTypeManager.AllTypeInfo
    # @return a tuple of (enumTypes, dataTypes, managedTypes)
    def _ConvertAllTypes(self, allTypes):
        """ Convert all dynamic types to pyVmomi type definitions """
        # Generate lists good for VmomiSupport.CreateXYZType
        enumTypes = self._Filter(self._ConvertEnumType, allTypes.enumTypeInfo)
        dataTypes = self._Filter(self._ConvertDataType, allTypes.dataTypeInfo)
        managedTypes = self._Filter(self._ConvertManagedType,
                                    allTypes.managedTypeInfo)
        retAllTypes = (enumTypes, dataTypes, managedTypes)
        return retAllTypes

    # Create pyVmomi types from pyVmomi type definitions
    #
    # @param  enumTypes pyVmomi enum type definitions
    # @param  dataTypes pyVmomi data type definitions
    # @param  managedTypes pyVmomi managed type definitions
    def _CreateAllTypes(self, enumTypes, dataTypes, managedTypes):
        """ Create pyVmomi types from pyVmomi type definitions """

        # Create versions
        for typeInfo in managedTypes:
            name = typeInfo[0]
            version = typeInfo[3]
            VmomiSupport.AddVersion(version, '', '1.0', 0, name)
            VmomiSupport.AddVersionParent(version, 'vmodl.version.version0')
            VmomiSupport.AddVersionParent(version, 'vmodl.version.version1')
            VmomiSupport.AddVersionParent(version, version)

        # Create partial types
        for fn, infos in (VmomiSupport.CreateEnumType, enumTypes), \
                         (VmomiSupport.CreateDataType, dataTypes), \
                         (VmomiSupport.CreateManagedType, managedTypes):
            for typeInfo in infos:
                try:
                    fn(*typeInfo)
                except Exception:
                    # Ignore errors due to duplicate importing
                    pass

    def _ConvertAnnotations(self, annotations):
        """ Convert annotations to pyVmomi flags """
        flags = 0
        if annotations:
            for annotation in annotations:
                flags |= self._mapFlags.get(annotation.name, 0)
        return flags

    @staticmethod
    def _Filter(fn, types):
        """ Call fn for each non null element in types. Similiar to filter """
        if types:
            return [fn(prop) for prop in types if prop is not None]
        else:
            return []

    def _ConvertParamType(self, paramType):
        """Convert vmodl.reflect.DynamicTypeManager. ParamTypeInfo to pyVmomi
        param definition
        """
        if paramType:
            name = paramType.name
            version = paramType.version
            aType = paramType.type
            flags = self._ConvertAnnotations(paramType.annotation)
            privId = paramType.privId
            param = (name, aType, version, flags, privId)
        else:
            param = None
        return param

    def _ConvertMethodType(self, methodType):
        """Convert vmodl.reflect.DynamicTypeManager.MethodTypeInfo to
        pyVmomi method definition
        """
        if methodType:
            name = methodType.name
            wsdlName = methodType.wsdlName
            version = methodType.version
            params = self._Filter(self._ConvertParamType,
                                  methodType.paramTypeInfo)
            privId = methodType.privId
            faults = methodType.fault

            # Figure out reture info
            if methodType.returnTypeInfo:
                returnTypeInfo = methodType.returnTypeInfo
                retFlags = self._ConvertAnnotations(returnTypeInfo.annotation)
                methodRetType = returnTypeInfo.type
            else:
                retFlags = 0
                methodRetType = "void"
            if wsdlName.endswith("_Task"):
                # TODO: Need a seperate task return type for task, instead of
                #       hardcode vim.Task as return type
                retType = "vim.Task"
            else:
                retType = methodRetType
            retInfo = (retFlags, retType, methodRetType)

            method = (name, wsdlName, version, params, retInfo, privId, faults)
        else:
            method = None
        return method

    def _ConvertManagedPropertyType(self, propType):
        """Convert vmodl.reflect.DynamicTypeManager.PropertyTypeInfo to
        pyVmomi managed property definition
        """
        if propType:
            name = propType.name
            version = propType.version
            aType = propType.type
            flags = self._ConvertAnnotations(propType.annotation)
            privId = propType.privId
            prop = (name, aType, version, flags, privId)
        else:
            prop = None
        return prop

    def _ConvertManagedType(self, managedType):
        """Convert vmodl.reflect.DynamicTypeManager.ManagedTypeInfo to
        pyVmomi managed type definition
        """
        if managedType:
            vmodlName = managedType.name
            wsdlName = managedType.wsdlName
            version = managedType.version
            parent = managedType.base[0]
            props = self._Filter(self._ConvertManagedPropertyType,
                                 managedType.property)
            methods = self._Filter(self._ConvertMethodType, managedType.method)
            moType = (vmodlName, wsdlName, parent, version, props, methods)
        else:
            moType = None
        return moType

    def _ConvertDataPropertyType(self, propType):
        """Convert vmodl.reflect.DynamicTypeManager.PropertyTypeInfo to pyVmomi
        data property definition
        """
        if propType:
            name = propType.name
            version = propType.version
            aType = propType.type
            flags = self._ConvertAnnotations(propType.annotation)
            prop = (name, aType, version, flags)
        else:
            prop = None
        return prop

    def _ConvertDataType(self, dataType):
        """Convert vmodl.reflect.DynamicTypeManager.DataTypeInfo to pyVmomi
        data type definition
        """
        if dataType:
            vmodlName = dataType.name
            wsdlName = dataType.wsdlName
            version = dataType.version
            parent = dataType.base[0]
            props = self._Filter(self._ConvertDataPropertyType,
                                 dataType.property)
            doType = (vmodlName, wsdlName, parent, version, props)
        else:
            doType = None
        return doType

    def _ConvertEnumType(self, enumType):
        """Convert vmodl.reflect.DynamicTypeManager.EnumTypeInfo to pyVmomi
        enum type definition
        """
        if enumType:
            vmodlName = enumType.name
            wsdlName = enumType.wsdlName
            version = enumType.version
            values = enumType.value
            enumType = (vmodlName, wsdlName, version, values)
        else:
            enumType = None
        return enumType
