/*
 * Copyright 2014-2021 Firestack, all rights reserved.
 */
import { Class, ix, PromUtil } from 'ts-basis';
import { MoBaseDetails, Permission } from '../../models/mo/mo.general';
import * as FolderTypes from '../../models/mo/mo.folder.models';
import * as ClusterTypes from '../../models/mo/mo.cluster.models';
import * as HostTypes from '../../models/mo/mo.host.models';
import * as DatastoreTypes from '../../models/mo/mo.datastore.models';
import * as NetworkTypes from '../../models/mo/mo.network.models';
import * as RespoolTypes from '../../models/mo/mo.respool.models';
import * as VMTypes from '../../models/mo/mo.vm.models';
import * as VSwitchTypes from '../../models/mo/mo.vswitch.models';
import { moref, PartialAny } from '../../models/mo.props';
import { VsphereDatacenter } from './vsphere';


export namespace Mo {

  export class Base extends ix.Entity implements MoBaseDetails<Mo.Base> {
    $dc: VsphereDatacenter = null;
    $type = 'Base';
    $dirty = false;
    $initPending: boolean | Promise<boolean> = true;
    $defunct = false;
    $guid: string = null;
    $srcBefore: any = null;
    $src: any = null;
    $deletedTime: number = 0;
    $lastSync: number = 0;
    $syncing: Promise<boolean> = null;

    iid: moref = '';
    name: string = '';
    parent: Mo.Base = null;
    overallStatus: string;
    headers: { [header: string]: string} = {};

    constructor($dc: VsphereDatacenter, init: any, preinit?: (thisRef: Mo.Base) => any) {
      super('ManagedObject', '$guid:' + $dc.key + ':' + init.iid);
      $dc.lifecycle.manage(this);
      if (preinit) { preinit(this); }
      this.$dc = $dc;
      this.$guid = '$guid:' + $dc.key + ':' + init.iid;
      this.iid = init.iid;
      this.$dc.inventory.byGUID[this.$guid] = this;
      this.$dc.inventory.byIID[this.iid] = this;
      this.updateFrom(init, true);
      this.$dc.emitEvent(this.$dc.entryRegister$, { target: this });
    }

    async updateFrom(init: any, fromBase = false) {
      this.$srcBefore = this.$src;
      this.$src = init;
      this.setName(init);
      if (fromBase) {
        this.overallStatus = init.overallStatus ? init.overallStatus : '';
        this.$lastSync = Date.now();
      }
      await this.updateFromExtended(init);
      this.$dc.emitEvent(this.$dc.entryRefresh$, { target: this });
      if (this.$src?.parent !== this.$srcBefore?.parent) {
        this.$dc.emitEvent(this.$dc.entryMove$, { target: this, from: this.$srcBefore?.parent, to: this.$src?.parent });
      }
      return this;
    }
    async updateFromExtended(_: any): Promise<any> {}
    unregister() {
      if (this.$defunct) { return; }
      this.$defunct = true;
      this.$deletedTime = Date.now();
      this.$dc.inventory.deleted.push(this);
      delete this.$dc.inventory.byGUID[this.$guid];
      delete this.$dc.inventory.byIID[this.iid];
      this.unregisterExtended();
      this.unsetName();
    }
    unregisterExtended() {}
    setName(init: any) {
      if (!init.name) {
        if (this instanceof Mo.ResourcePool) {
          init.name = init.summary.name;
        } else {
          init.name = this.iid.split(':').pop();
          this.$dc.debugOutput(`Cannot resolve entity name of ${this.iid}, using ${init.name} instead.`);
        }
      }
      if (init.name.indexOf('|') > 0) {
        init.name = init.name.replace(/\|/g, '%7C');
      }
      if (this.name !== init.name) {
        this.unsetName();
        this.name = init.name;
        if (!this.$dc.index.byName[this.name]) { this.$dc.index.byName[this.name] = []; }
        this.$dc.index.byName[this.name].push(this.iid);
      }
    }
    unsetName(name?: string) {
      if (!name) { name = this.name; }
      if (!name) { return; }
      const nameReg = this.$dc.index.byName[name];
      if (nameReg) {
        const nameIdx = nameReg.indexOf(this.iid);
        if (nameIdx >= 0) { nameReg.splice(nameIdx, 1); }
        if (nameReg.length === 0) { delete this.$dc.index.byName[name]; }
      }
    }
    getSourceData() { return this.$src; }
    getSourceDataBefore() { return this.$srcBefore; }
    toString() { return this.$guid; }
    toJSON() { return this.$guid; }
    entitySummaryHeaders() { return this.$guid + '|' + this.name + '|' + (this.parent ? this.parent.$guid : '') + '|' + this.getSerializedHeaders(); }
    getSerializedHeaders() {
      const kvPairs: string[] = [];
      for (const header of Object.keys(this.headers)) {
        const value = encodeURIComponent(this.headers[header]);
        kvPairs.push(`${header}=${value}`);
      }
      return kvPairs.join('&');
    }
    moBaseSerializable() {
      return {
        $guid: this.$guid,
        $type: this.$type,
        $deletedTime: this.$deletedTime,
        $lastSync: this.$lastSync,
        iid: this.iid,
        name: this.name,
        parent: this.parent,
        overallStatus: this.overallStatus,
      };
    }
    serialize() {
      return JSON.stringify({ ...this.moBaseSerializable() });
    }
    refresh(): Promise<boolean> {
      if (this.$syncing) { return this.$syncing; }
      return this.$syncing = new Promise<boolean>(async resolve => {
        try {
          const conn = this.$dc.pickConnection();
          if (!conn) { return this.$dc.errorNoConnection(resolve); }
          const data = await conn.api.getManagedObjectData(this.iid);
          if (data) { await this.updateFrom(data); }
          this.$syncing = null;
          resolve(data !== null);
        } catch (e) { this.$dc.ix.pushError(e); resolve(null); }
      });
    }
  }

  export class Folder extends Mo.Base implements FolderTypes.FolderFullDetails<Mo.Base, Mo.Base> {
    childEntity: Mo.Base[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.Folder>) {
      super($dc, init, this2 => this2.$type = 'Folder');
      $dc.inventory.folder[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.Folder>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.childEntity.length = 0;
          if (init.childEntity) {
            for (const iid of init.childEntity) { findMo($dc, iid, Mo.Base, mo => this.childEntity.push(mo), proms); }
          }
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.folder[this.iid]; }
    moFolderSerializable() {
      return {
        childEntity: this.childEntity,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moFolderSerializable(),
      });
    }
  }

  export class ComputeResource extends Mo.Base implements ClusterTypes.ClusterFullDetails<Mo.Base, Mo.HostSystem, Mo.Datastore, Mo.Network, Mo.ResourcePool> {
    configurationEx: any;
    datastore: Mo.Datastore[] = [];
    datastoreSummary: DatastoreTypes.DatastoreSummary[] = [];
    drsFault: any;
    drsRecommendation: any;
    host: Mo.HostSystem[] = [];
    lifecycleManaged: boolean;
    migrationHistory: any;
    network: Mo.Network[] = [];
    recommendation: any;
    resourcePool: Mo.ResourcePool;
    summary: ClusterTypes.ClusterComputeResourceSummary;
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.ComputeResource>) {
      super($dc, init, this2 => this2.$type = 'ComputeResource');
      $dc.inventory.computeResource[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.ComputeResource>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.host.length = 0;
          this.datastore.length = 0;
          this.datastoreSummary.length = 0;
          this.network.length = 0;
          if (init.host) {
            for (const iid of init.host) { findMo($dc, iid, Mo.HostSystem, mo => this.host.push(mo), proms); }
          }
          if (init.datastore) {
            for (const iid of init.datastore) { findMo($dc, iid, Mo.Datastore, mo => {
              this.datastoreSummary.push(mo.summary); this.datastore.push(mo);
            }, proms); }
          }
          if (init.network) {
            for (const iid of init.network) { findMo($dc, iid, Mo.Network, mo => this.network.push(mo), proms); }
          }
          this.configurationEx = init.configurationEx;
          this.drsFault = init.drsFault;
          this.drsRecommendation = init.drsRecommendation;
          this.lifecycleManaged = init.lifecycleManaged;
          this.migrationHistory = init.migrationHistory;
          this.recommendation = init.recommendation;
          this.summary = init.summary;
          findMo($dc, init.resourcePool, Mo.ResourcePool, mo => this.resourcePool = mo, proms);
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.computeResource[this.iid]; }
    moComputeResourceSerializable() {
      return {
        configurationEx: this.configurationEx,
        datastore: this.datastore,
        datastoreSummary: this.datastoreSummary,
        drsFault: this.drsFault,
        drsRecommendation: this.drsRecommendation,
        host: this.host,
        lifecycleManaged: this.lifecycleManaged,
        migrationHistory: this.migrationHistory,
        network: this.network,
        recommendation: this.recommendation,
        resourcePool: this.resourcePool,
        summary: this.summary,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moComputeResourceSerializable(),
      });
    }
  }

  export class HostSystem extends Mo.Base implements HostTypes.HostFullDetails<Mo.Base, Mo.Datastore, Mo.Network, Mo.VirtualMachine> {
    capability: HostTypes.HostCapability;
    config: HostTypes.HostConfigInfo;
    configManager: HostTypes.HostConfigManager;
    datastore: Mo.Datastore[] = [];
    datastoreSummary: DatastoreTypes.DatastoreSummary[] = [];
    hardware: HostTypes.HostHardware;
    network: Mo.Network[] = [];
    licensableResource: HostTypes.HostLicensableResourceInfo;
    runtime: HostTypes.HostRuntimeInfo;
    summary: HostTypes.HostListSummary;
    systemResources: HostTypes.HostConfigResourceAllocTree;
    vm: Mo.VirtualMachine[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.HostSystem>) {
      super($dc, init, this2 => this2.$type = 'HostSystem');
      $dc.inventory.hostSystem[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.HostSystem>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.datastore.length = 0;
          this.datastoreSummary.length = 0;
          this.network.length = 0;
          this.vm.length = 0;
          if (init.datastore) {
            for (const iid of init.datastore) { findMo($dc, iid, Mo.Datastore, mo => {
              this.datastoreSummary.push(mo.summary); this.datastore.push(mo);
            }, proms); }
          }
          if (init.network) {
            for (const iid of init.network) { findMo($dc, iid, Mo.Network, mo => this.network.push(mo), proms); }
          }
          if (init.vm) {
            for (const iid of init.vm) { findMo($dc, iid, Mo.VirtualMachine, mo => this.vm.push(mo), proms); }
          }
          this.capability = init.capability;
          this.config = init.config;
          this.configManager = init.configManager;
          this.hardware = init.hardware;
          this.licensableResource = init.licensableResource;
          this.overallStatus = init.overallStatus;
          this.runtime = init.runtime;
          this.summary = init.summary;
          this.systemResources = init.systemResources;
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.hostSystem[this.iid]; }
    moHostSystemSerializable() {
      return {
        capability: this.capability,
        config: this.config,
        configManager: this.configManager,
        datastore: this.datastore,
        datastoreSummary: this.datastoreSummary,
        hardware: this.hardware,
        licensableResource: this.licensableResource,
        network: this.network,
        overallStatus: this.overallStatus,
        runtime: this.runtime,
        summary: this.summary,
        systemResources: this.systemResources,
        vm: this.vm,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moHostSystemSerializable(),
      });
    }
  }

  export class Datacenter extends Mo.Base {
    datastore: Mo.Datastore[] = [];
    datastoreFolder: Mo.Folder;
    hostFolder: Mo.Folder;
    network: Mo.Network[] = [];
    networkFolder: Mo.Folder;
    vmFolder: Mo.Folder;
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.Datacenter>) {
      super($dc, init, this2 => this2.$type = 'Datacenter');
      $dc.inventory.datacenter[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.Datacenter>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.datastore.length = 0;
          this.network.length = 0;
          if (init.datastore) {
            for (const iid of init.datastore) { findMo($dc, iid, Mo.Datastore, mo => this.datastore.push(mo), proms); }
          }
          if (init.network) {
            for (const iid of init.network) { findMo($dc, iid, Mo.Network, mo => this.network.push(mo), proms); }
          }
          findMo($dc, init.datastoreFolder, Mo.Folder, mo => this.datastoreFolder = mo, proms);
          findMo($dc, init.hostFolder, Mo.Folder, mo => this.hostFolder = mo, proms);
          findMo($dc, init.networkFolder, Mo.Folder, mo => this.networkFolder = mo, proms);
          findMo($dc, init.vmFolder, Mo.Folder, mo => this.vmFolder = mo, proms);
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.datacenter[this.iid]; }
    moDatacenterSerializable() {
      return {
        datastore: this.datastore,
        datastoreFolder: this.datastoreFolder,
        hostFolder: this.hostFolder,
        network: this.network,
        networkFolder: this.networkFolder,
        vmFolder: this.vmFolder,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moDatacenterSerializable(),
      });
    }
  }

  export class Datastore extends Mo.Base implements DatastoreTypes.DatastoreFullDetails<Mo.Base, Mo.Datastore, Mo.VirtualMachine> {
    aliasOf: Mo.Datastore; 
    aliases: Mo.Datastore[] = [];
    capability: DatastoreTypes.DatastoreCapability;
    browser: moref;
    host: DatastoreTypes.DatastoreHostMount[] = [];
    info: DatastoreTypes.VsanDatastoreInfo;
    iormConfiguration?: DatastoreTypes.StorageIORMInfo;
    summary: DatastoreTypes.DatastoreSummary;
    vm: Mo.VirtualMachine[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.Datastore>) {
      super($dc, init, this2 => this2.$type = 'Datastore');
      $dc.inventory.datastore[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.Datastore>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.host.length = 0;
          this.vm.length = 0;
          if (init.vm) {
            for (const iid of init.vm) { findMo($dc, iid, Mo.VirtualMachine, mo => this.vm.push(mo), proms); }
          }
          this.host = init.host;
          this.browser = init.browser;
          this.capability = init.capability;
          this.info = init.info;
          this.iormConfiguration = init.iormConfiguration;
          this.summary = init.summary;
          this.summary.isAlias = (this.info.containerId !== this.info.aliasOf);
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.datastore[this.iid]; }
    moDatastoreSerializable() {
      return {
        aliasOf: this.aliasOf,
        aliases: this.aliases,
        browser: this.browser,
        capability: this.capability,
        host: this.host,
        info: this.info,
        iormConfiguration: this.iormConfiguration,
        summary: this.summary,
        vm: this.vm,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moDatastoreSerializable(),
      });
    }
  }

  export class Network extends Mo.Base implements NetworkTypes.NetworkFullDetails<Mo.Base, Mo.HostSystem, Mo.VirtualMachine> {
    capability: {[featureName: string]: boolean} = {};
    extraConfig: any;
    host: Mo.HostSystem[] = [];
    summary: NetworkTypes.NetworkSummary;
    vm: Mo.VirtualMachine[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.Network>) {
      super($dc, init, this2 => this2.$type = 'Network');
      $dc.inventory.network[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.Network>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.host.length = 0;
          this.vm.length = 0;
          if (init.host) {
            for (const iid of init.host) { findMo($dc, iid, Mo.HostSystem, mo => this.host.push(mo), proms); }
          }
          if (init.vm) {
            for (const iid of init.vm) { findMo($dc, iid, Mo.VirtualMachine, mo => this.vm.push(mo), proms); }
          }
          this.capability = init.capability;
          this.extraConfig = init.extraConfig;
          this.summary = init.summary;
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.network[this.iid]; }
    moNetworkSerializable() {
      return {
        capability: this.capability,
        extraConfig: this.extraConfig,
        host: this.host,
        summary: this.summary,
        vm: this.vm,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moNetworkSerializable(),
      });
    }
  }

  export class ResourcePool extends Mo.Base implements RespoolTypes.ResourcePoolFullDetails<Mo.Base, Mo.ComputeResource, Mo.VirtualMachine> {
    childConfiguration: RespoolTypes.ResourceConfigSpec[] = [];
    config: RespoolTypes.ResourceConfigSpec;
    owner: Mo.ComputeResource;
    resourcePool: Mo.ResourcePool[] = [];
    runtime: RespoolTypes.ResourcePoolRuntimeInfo;
    summary: RespoolTypes.ResourcePoolSummary;
    vm: Mo.VirtualMachine[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.ResourcePool>, preinit?: (thisRef: Base) => any) {
      super($dc, init, preinit ? preinit : this2 => this2.$type = 'ResourcePool');
      $dc.inventory.resourcePool[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.ResourcePool>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        try {
          const $dc = this.$dc;
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.resourcePool.length = 0;
          this.vm.length = 0;
          if (init.resourcePool) {
            for (const iid of init.resourcePool) {
              findMo($dc, iid, Mo.ResourcePool, mo => { mo.parent = this; this.resourcePool.push(mo) }, proms);
            }
          }
          if (init.vm) {
            for (const iid of init.vm) { findMo($dc, iid, Mo.VirtualMachine, mo => this.vm.push(mo), proms); }
          }
          this.childConfiguration = init.childConfiguration;
          this.config = init.config;
          this.runtime = init.runtime;
          this.summary = init.summary;
          findMo($dc, init.owner, Mo.ComputeResource, mo => this.owner = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.resourcePool[this.iid]; }
    moResourcePoolSerializable() {
      return {
        childConfiguration: this.childConfiguration,
        config: this.config,
        owner: this.owner,
        resourcePool: this.resourcePool,
        runtime: this.runtime,
        summary: this.summary,
        vm: this.vm,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moResourcePoolSerializable(),
      });
    }
  }

  export class VirtualApp extends Mo.ResourcePool {
    childConfiguration: any;
    config: any;
    datastore: Mo.Datastore[] = [];
    network: Mo.Network[] = [];
    owner: Mo.ComputeResource;
    parentFolder: Mo.Folder;
    parentVApp: Mo.VirtualApp;
    resourcePool: Mo.ResourcePool[] = [];
    runtime: any;
    summary: any;
    vAppConfig: any;
    vm: Mo.VirtualMachine[] = [];
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.VirtualApp>) {
      super($dc, init, this2 => this2.$type = 'VirtualApp');
      $dc.inventory.virtualApp[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.VirtualApp>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.datastore.length = 0;
          this.network.length = 0;
          this.resourcePool.length = 0;
          this.vm.length = 0;
          if (init.datastore) {
            for (const iid of init.datastore) { findMo($dc, iid, Mo.Datastore, mo => this.datastore.push(mo), proms); }
          }
          if (init.network) {
            for (const iid of init.network) { findMo($dc, iid, Mo.Network, mo => this.network.push(mo), proms); }
          }
          if (init.resourcePool) {
            for (const iid of init.resourcePool) {
              findMo($dc, iid, Mo.ResourcePool, mo => { mo.parent = this; this.resourcePool.push(mo); }, proms);
            }
          }
          if (init.vm) {
            for (const iid of init.vm) { findMo($dc, iid, Mo.VirtualMachine, mo => this.vm.push(mo), proms); }
          }
          this.childConfiguration = init.childConfiguration;
          this.config = init.config;
          this.runtime = init.runtime;
          this.summary = init.summary;
          this.vAppConfig = init.vAppConfig;
          findMo($dc, init.parentFolder, Mo.Folder, mo => this.parentFolder = mo, proms);
          findMo($dc, init.parentVApp, Mo.VirtualApp, mo => this.parentVApp = mo, proms);
          findMo($dc, init.owner, Mo.ComputeResource, mo => this.owner = mo, proms);
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.virtualApp[this.iid]; }
    moVirtualAppSerializable() {
      return {
        childConfiguration: this.childConfiguration,
        config: this.config,
        datastore: this.datastore,
        network: this.network,
        owner: this.owner,
        parentFolder: this.parentFolder,
        parentVApp: this.parentVApp,
        resourcePool: this.resourcePool,
        runtime: this.runtime,
        summary: this.summary,
        vAppConfig: this.vAppConfig,
        vm: this.vm,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moVirtualAppSerializable(),
      });
    }
  }

  export class VirtualMachine extends Mo.Base implements VMTypes.VirtualMachineFullDetails<Mo.Base, Mo.Datastore, Mo.Network, Mo.ResourcePool> {
    $registeredIP: string;
    capability: VMTypes.VirtualMachineCapability;
    config: VMTypes.VirtualMachineConfig;
    datastore: Mo.Datastore[] = [];
    guest: VMTypes.GuestInfo;
    guestHeartbeatStatus: string;
    layout: VMTypes.VirtualMachineFileLayout;
    network: Mo.Network[] = [];
    resourceConfig: RespoolTypes.ResourceConfigSpec; 
    resourcePool: Mo.ResourcePool;
    runtime: VMTypes.VirtualMachineRuntimeInfo;
    storage: VMTypes.VirtualMachineStorageInfo;
    summary: VMTypes.VirtualMachineSummary;
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.VirtualMachine>) {
      super($dc, init, this2 => {
        (this2 as VirtualMachine).$registeredIP = null;
        this2.$type = 'VirtualMachine';
      });
      $dc.inventory.virtualMachine[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.VirtualMachine>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.datastore.length = 0;
          this.network.length = 0;
          if (init.datastore) {
            for (const iid of init.datastore) { findMo($dc, iid, Mo.Datastore, mo => this.datastore.push(mo), proms); }
          }
          if (init.network) {
            for (const iid of init.network) { findMo($dc, iid, Mo.Network, mo => this.network.push(mo), proms); }
          }
          this.linkIP(init);
          this.capability = init.capability;
          this.config = init.config;
          this.guest = init.guest;
          this.guestHeartbeatStatus = init.guestHeartbeatStatus;
          this.layout = init.layout;
          this.resourceConfig = init.resourceConfig;
          this.runtime = init.runtime;
          this.storage = init.storage;
          this.summary = init.summary;
          findMo($dc, init.resourcePool, Mo.ResourcePool, mo => this.resourcePool = mo, proms);
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() {
      delete this.$dc.inventory.virtualMachine[this.iid];
      this.unlinkIP();
    }
    linkIP(init: PartialAny<Mo.VirtualMachine>) {
      if (this.$registeredIP) { this.unlinkIP(); }
      if (init.guest?.ipAddress) {
        const ip = init.guest.ipAddress;
        if (!this.$dc.index.byIP[ip]) { this.$dc.index.byIP[ip] = []; }
        this.$dc.index.byIP[ip].push(this.iid);
        this.$registeredIP = ip;
      }
    }
    unlinkIP() {
      if (this.$registeredIP) {
        const ipReg = this.$dc.index.byIP[this.$registeredIP];
        if (ipReg) {
          const ipIdx = ipReg.indexOf(this.iid);
          if (ipIdx >= 0) { ipReg.splice(ipIdx, 1); }
          if (ipReg.length === 0) { delete this.$dc.index.byIP[this.$registeredIP]; }
        }
      }
    }
    moVirtualMachineSerializable() {
      return {
        $registeredIP: this.$registeredIP,
        capability: this.capability,
        config: this.config,
        datastore: this.datastore,
        guest: this.guest,
        guestHeartbeatStatus: this.guestHeartbeatStatus,
        layout: this.layout,
        network: this.network,
        resourceConfig: this.resourceConfig,
        resourcePool: this.resourcePool,
        runtime: this.runtime,
        storage: this.storage,
        summary: this.summary,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moVirtualMachineSerializable(),
      });
    }
  }

  export class VirtualSwitch extends Mo.Base implements VSwitchTypes.VirtualSwitchFullDetails<Mo.Base, Mo.Network> {
    capability: VSwitchTypes.DVSCapability;
    config: VSwitchTypes.DVSConfigInfo;
    configStatus: string;
    permission: Permission[] = [];
    portgroup: Mo.Network[] = [];
    runtime: VSwitchTypes.DVSRuntimeInfo;
    summary: VSwitchTypes.DVSSummary;
    uuid: string;
    constructor($dc: VsphereDatacenter, init: PartialAny<Mo.VirtualSwitch>) {
      super($dc, init, this2 => this2.$type = 'VirtualSwitch');
      $dc.inventory.virtualSwitch[this.iid] = this;
      this.updateFrom(init);
    }
    async updateFromExtended(init: PartialAny<Mo.VirtualSwitch>) {
      return this.$initPending = new Promise<boolean>(async resolve => {
        const $dc = this.$dc;
        try {
          await Promise.resolve($dc.inventoryStubPending);
          const proms = [];
          this.portgroup.length = 0;
          if (init.portgroup) {
            for (const iid of init.portgroup) { findMo($dc, iid, Mo.Network, mo => this.portgroup.push(mo), proms); }
          }
          this.capability = init.capability;
          this.config = init.config;
          this.configStatus = init.configStatus;
          this.permission = init.permission;
          this.runtime = init.runtime;
          this.summary = init.summary;
          this.uuid = init.uuid;
          findMo($dc, init.parent, Mo.Base, mo => this.parent = mo, proms);
          await PromUtil.allSettled(proms);
        } catch (e) { this.$dc.debugOutput(e); }
        this.$initPending = false;
        resolve(false);
      });
    }
    unregisterExtended() { delete this.$dc.inventory.virtualSwitch[this.iid]; }
    moVirtualSwitchSerializable() {
      return {
        capability: this.capability,
        config: this.config,
        configStatus: this.configStatus,
        permission: this.permission,
        portgroup: this.portgroup,
        runtime: this.runtime,
        summary: this.summary,
        uuid: this.uuid,
      };
    }
    serialize() {
      return JSON.stringify({
        ...this.moBaseSerializable(),
        ...this.moVirtualSwitchSerializable(),
      });
    }
  }

}

export function getMoTypeByName(moTypeName: string) {
  switch (moTypeName) {
    case 'Base': return Mo.Base;
    case 'Folder': return Mo.Folder;
    case 'HostSystem': return Mo.HostSystem;
    case 'Datacenter': return Mo.Datacenter;
    case 'Network': return Mo.Network;
    case 'Datastore': return Mo.Datastore;
    case 'ComputeResource': return Mo.ComputeResource;
    case 'ResourcePool': return Mo.ResourcePool;
    case 'VirtualApp': return Mo.VirtualApp;
    case 'VirtualMachine': return Mo.VirtualMachine;
    case 'DistributedVirtualPortgroup': return Mo.Network;
    case 'VirtualSwitch':
    case 'DistributedVirtualSwitch':
    case 'VmwareDistributedVirtualSwitch':
      return Mo.VirtualSwitch;
    default: console.log(`Unknown managed object type ${moTypeName}`); break;
  }
  return null;
}

export function iidParse(iid: string) {
  if (iid.startsWith('$mo:')) {
    return iid;
  } else {
    const valueLit = iid.split('-');
    const valueType = valueLit[0];
    const valueSubtype = valueLit[1];
    switch (valueType) {
      case 'group': return `$mo:Folder:${iid}`;
      case 'datacenter': return `$mo:Datacenter:${iid}`;
      case 'network':
      case 'dvportgroup':
        return `$mo:Network:${iid}`;
      case 'datastore': return `$mo:Datastore:${iid}`;
      case 'domain': return `$mo:ComputeResource:${iid}`;
      case 'resgroup':
        if (valueSubtype.startsWith('v')) {
          return `$mo:VirtualApp:${iid}`;
        } else {
          return `$mo:ResourcePool:${iid}`;
        }
      case 'host': return `$mo:HostSystem:${iid}`;
      case 'vm': return `$mo:VirtualMachine:${iid}`;
      case 'dvs': return `$mo:VirtualSwitch:${iid}`;
      default: console.log(`Unknown mo value ${iid}`); return null;
    }
  }
}

export function findMo<T = any>($dc: VsphereDatacenter, iid: string, typeClass?: Class<T>,
                                onresolve?: (mo: T) => any, proms?: Promise<any>[]) {
  const prom = new Promise<T>(async resolve => {
    let mo = $dc.inventory.byIID[iid] as any;
    if (!mo && iid) {
      $dc.debugKeep(`${iid} not found during linkage; fetching...`);
      mo = await $dc.get(iid, typeClass);
      $dc.debugKeep(`${iid} fetched`);
    }
    if (onresolve) { onresolve(mo); }
    return resolve(mo as T);
  });
  if (proms) { proms.push(prom); }
  return prom;
}
