using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Web.Services.Protocols;
using System.IO;
using System.Globalization;
using ConverterApi;


namespace ConverterSamples
{
   class ConverterOperation
   {
      /// <summary>
      /// Type of the operation to perform.
      /// One instance of ConverterOperation will perform one operation.
      /// </summary>
      enum OperationType
      {
         Convert,
         Reconfig,
         Synchronize,   // Schedule incremental task of a synchronizable Windows P2V job
         QuerySource,   // Prints source machine info
         GetDefaults,   // Calls ValidateTargetAndGetDefaults and prints default
                        // conversion parameters
         List,          // List recent jobs and tasks
      }

      /// <summary>
      /// Type of the conversion source.
      /// Not applicable to operations of type List.
      /// </summary>
      enum SourceType
      {
         Invalid,

         Physical,
         Managed,	// VC, ESX
         Hosted,	// WS, Player, Fusion. Also third party images and backups
         HyperV,
      }

      /// <summary>
      /// Type of the source machine OS
      /// </summary>
      enum OsType
      {
         Invalid,

         windowsOs,
         linuxOs,
      }

      /// <summary>
      /// Type of the destination VM
      /// Applicable only to operations of type Convert
      /// </summary>
      enum TargetType
      {
         Invalid,

         Managed,	// VC, ESX
         Hosted,	// WS, Player, Fusion
      }

      /// <summary>
      /// Type of cloning
      /// Applicable only to operations of type Convert
      /// </summary>
      enum CloningMode
      {
         Invalid,

         diskBasedCloning,
         volumeBasedCloning,
         linkedClone,
      }



      // Conversion Job details
      OperationType[] _operations = new OperationType[0];
      SourceType _sourceType = SourceType.Invalid;
      TargetType _targetType = TargetType.Invalid;
      private int _jobId = 0;
      private bool _jobSync = false;
      private DateTime _jobSyncStart = DateTime.MinValue;
      private bool _jobSyncFinalize;

      // Source details
      private String _sourcePhysAddress;
      private String _sourcePhysUsername;
      private String _sourcePhysPassword;
      private String _sourcePhysKeyPath;
      private OsType _sourcePhysOsType = OsType.Invalid;
      private int _sourcePhysAgentPort = 0; /*9089*/
      private String _sourcePhysThumbprint;
      private bool _sourcePhysReboot = false;
      private String _sourceManagedAddress;
      private String _sourceManagedThumbprint;
      private String _sourceManagedUsername;
      private String _sourceManagedPassword;
      private String _sourceManagedVm;
      private String _sourceHostedType;
      private String _sourceHostedConfigFilePath;
      private String _sourceHostedPassword;
      private String _sourceHostedNetworkUsername;
      private String _sourceHostedNetworkPassword;
      private String _sourceHypervHostname;
      private int _sourceHypervPort;
      private String _sourceHypervUsername;
      private String _sourceHypervPassword;
      private String _sourceHypervVmName;
      private String _sourceHypervVmGuid;
      private String _sourceHypervThumbprint;

      // Target details
      private String _targetManagedAddress;
      private String _targetManagedThumbprint;
      private String _targetManagedUsername;
      private String _targetManagedPassword;
      private String _targetManagedHost;
      private String _targetManagedResourcePool;
      private String _targetManagedCluster;
      private String _targetManagedVmFolder;
      private String _vmName;
      private String _targetHostedDirectory;
      private String _targetHostedNetworkUsername;
      private String _targetHostedNetworkPassword;

      // Target VM attributes
      private int _vmVcpu = 0;
      private int _vmMemory = 0;
      private int _coresPerSocket = 0;

      // Storage parameters
      CloningMode _cloningMode = CloningMode.Invalid;
      String _diskControllerType;
      bool _optimizeAlignment = true; // Default value if not set
      ConverterStorageParamsTargetDiskParams[] _targetDiskParams;

      // List
      int _tasks;
      int _jobs;

      private ConverterConnection _converterServer = null;
      String _propertiesFile;

      /// <summary>
      /// Constructor
      /// </summary>
      /// <param name="converterServer">Connection to converter server</param>
      /// <param name="propertiesFile">Path to file with operation details</param>
      public ConverterOperation(ConverterConnection converterServer, String propertiesFile)
      {
         _converterServer = converterServer;
         _propertiesFile = propertiesFile;
      }

      /// <summary>
      /// Execute the operation
      /// </summary>
      public void Execute()
      {
         TextReader textReader = null;

         try {
            textReader = new StreamReader(_propertiesFile);
            GetInputFromFile(textReader);
         } catch (Exception e) {
            Console.Error.WriteLine("Failed to load input file " + _propertiesFile);
            Console.Error.WriteLine(e.ToString());
            return;
         } finally {
            if (textReader != null) {
               textReader.Close();
            }
         }

         String op = String.Empty;

         try {
            foreach (OperationType operation in _operations) {
               op = operation.ToString();
               Console.WriteLine("Executing operation '{0}' from properties file '{1}'",
                                 op, _propertiesFile);
               switch (operation) {
                  case OperationType.Convert:
                     Convert();
                     break;

                  case OperationType.Reconfig:
                     Reconfig();
                     break;

                  case OperationType.Synchronize:
                     Synchronize();
                     break;

                  case OperationType.QuerySource:
                     QuerySource();
                     break;

                  case OperationType.GetDefaults:
                     GetDefaults();
                     break;

                  case OperationType.List:
                     List();
                     break;

                  default:
                     throw new Exception("Invalid operation");
               }
            }
         } catch (Exception e) {
            Console.Error.WriteLine("Failed to execute operation '{0}' from file '{1}'",
                                    op, _propertiesFile);
            Console.Error.WriteLine(e.ToString());
         }
      }

      /// <summary>
      /// Parse the properties file and saves its data to member variables
      /// </summary>
      /// <param name="propertiesFile"></param>
      /// <returns></returns>
      private void GetInputFromFile(TextReader textReader)
      {
         for (String readLine = textReader.ReadLine(); readLine != null;
              readLine = textReader.ReadLine()) {
            // Remove comments
            if (readLine.Contains(";")) {
               readLine = readLine.Substring(0, readLine.IndexOf(';'));
            }

            readLine = readLine.Trim();

            if (String.IsNullOrEmpty(readLine)) {
               continue;
            }

            String[] props = readLine.Split(new Char[] { '=', '#' });

            switch (props[0]) {
               case "operation.count":
                  if (!String.IsNullOrEmpty(props[1])) {
                     int count = Int32.Parse(props[1]);
                     _operations = new OperationType[count];
                  }
                  break;
               case "operation":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int op = Int32.Parse(props[1]);
                     if (_operations != null && op < _operations.Length) {
                        _operations[op] =
                           (OperationType)Enum.Parse(typeof(OperationType), props[2]);
                     }
                  }
                  break;

               case "job.id":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _jobId = Int32.Parse(props[1]);
                  }
                  break;
               case "job.sync":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _jobSync = Boolean.Parse(props[1]);
                  }
                  break;
               case "job.sync.start":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _jobSyncStart = DateTime.Parse(props[1]);
                  }
                  break;
               case "job.sync.finalize":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _jobSyncFinalize = Boolean.Parse(props[1]);
                  }
                  break;

               case "source.type":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _sourceType = (SourceType)Enum.Parse(typeof(SourceType), props[1]);
                  }
                  break;
               case "source.physical.address":
                  _sourcePhysAddress = props[1];
                  break;
               case "source.physical.username":
                  _sourcePhysUsername = props[1];
                  break;
               case "source.physical.password":
                  _sourcePhysPassword = props[1];
                  break;
               case "source.physical.clientPrivateKey":
                  _sourcePhysKeyPath = props[1];
                  break;
               case "source.physical.ostype":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _sourcePhysOsType = (OsType)Enum.Parse(typeof(OsType), props[1]);
                  }
                  break;
               case "source.physical.agentport":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _sourcePhysAgentPort = Int32.Parse(props[1]);
                  }
                  break;
               case "source.physical.thumbprint":
                  _sourcePhysThumbprint = props[1];
                  break;
               case "source.physical.reboot":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _sourcePhysReboot = Boolean.Parse(props[1]);
                  }
                  break;
               case "source.managed.address":
                  _sourceManagedAddress = props[1];
                  break;
               case "source.managed.thumbprint":
                  _sourceManagedThumbprint = props[1];
                  break;
               case "source.managed.username":
                  _sourceManagedUsername = props[1];
                  break;
               case "source.managed.password":
                  _sourceManagedPassword = props[1];
                  break;
               case "source.managed.vm":
                  _sourceManagedVm = props[1];
                  break;
               case "source.hosted.type":
                  _sourceHostedType = props[1];
                  break;
               case "source.hosted.configfilepath":
                  _sourceHostedConfigFilePath = props[1];
                  break;
               case "source.hosted.password":
                  _sourceHostedPassword = props[1];
                  break;
               case "source.hosted.networkusername":
                  _sourceHostedNetworkUsername = props[1];
                  break;
               case "source.hosted.networkpassword":
                  _sourceHostedNetworkPassword = props[1];
                  break;
               case "source.hyperv.hostname":
                  _sourceHypervHostname = props[1];
                  break;
               case "source.hyperv.port":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _sourceHypervPort = Int32.Parse(props[1]);
                  }
                  break;
               case "source.hyperv.username":
                  _sourceHypervUsername = props[1];
                  break;
               case "source.hyperv.password":
                  _sourceHypervPassword = props[1];
                  break;
               case "source.hyperv.vmname":
                  _sourceHypervVmName = props[1];
                  break;
               case "source.hyperv.vmguid":
                  _sourceHypervVmGuid = props[1];
                  break;
               case "source.hyperv.thumbprint":
                  _sourceHypervThumbprint = props[1];
                  break;

               case "target.type":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _targetType = (TargetType)Enum.Parse(typeof(TargetType), props[1]);
                  }
                  break;
               case "target.managed.address":
                  _targetManagedAddress = props[1];
                  break;
               case "target.managed.thumbprint":
                  _targetManagedThumbprint = props[1];
                  break;
               case "target.managed.username":
                  _targetManagedUsername = props[1];
                  break;
               case "target.managed.password":
                  _targetManagedPassword = props[1];
                  break;
               case "target.managed.host":
                  _targetManagedHost = props[1];
                  break;
               case "target.managed.resourcepool":
                  _targetManagedResourcePool = props[1];
                  break;
               case "target.managed.cluster":
                  _targetManagedCluster = props[1];
                  break;
               case "target.managed.vmfolder":
                  _targetManagedVmFolder = props[1];
                  break;

               case "target.hosted.directory":
                  _targetHostedDirectory = props[1];
                  break;
               case "target.hosted.networkusername":
                  _targetHostedNetworkUsername = props[1];
                  break;
               case "target.hosted.networkpassword":
                  _targetHostedNetworkPassword = props[1];
                  break;

               case "vmtocreate.name":
                  _vmName = props[1];
                  break;
               case "vmtocreate.vcpu":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _vmVcpu = Int32.Parse(props[1]);
                  }
                  break;
               case "vmtocreate.corespersocket":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _coresPerSocket = Int32.Parse(props[1]);
                  }
                  break;
               case "vmtocreate.memory":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _vmMemory = Int32.Parse(props[1]);
                  }
                  break;

               case "storage.cloningmode":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _cloningMode = (CloningMode)Enum.Parse(typeof(CloningMode), props[1]);
                  }
                  break;
               case "storage.diskcontrollertype":
                  _diskControllerType = props[1];
                  break;

               case "storage.optimizealignment":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _optimizeAlignment = Boolean.Parse(props[1]);
                  }
                  break;

               case "storage.targetdisk.count":
                  if (!String.IsNullOrEmpty(props[1])) {
                     int count = Int32.Parse(props[1]);
                     switch (_targetType) {
                        case TargetType.Hosted:
                           _targetDiskParams =
                              new ConverterStorageParamsHostedTargetDiskParams[count];
                           for (int i = 0; i < count; ++i) {
                              _targetDiskParams[i] =
                                 new ConverterStorageParamsHostedTargetDiskParams();
                           }
                           break;

                        case TargetType.Managed:
                           _targetDiskParams =
                              new ConverterStorageParamsManagedTargetDiskParams[count];
                           for (int i = 0; i < count; ++i) {
                              _targetDiskParams[i] =
                                 new ConverterStorageParamsManagedTargetDiskParams();
                           }
                           break;

                        default:
                           throw new Exception("Invalid target type");
                     }
                  }
                  break;

               case "storage.targetdisk.sourcediskid":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        _targetDiskParams[disk].sourceDiskId = props[2];
                     }
                  }
                  break;

               case "storage.targetdisk.disktype":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        _targetDiskParams[disk].diskType = props[2];
                     }
                  }
                  break;

               case "storage.targetdisk.lvg":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        _targetDiskParams[disk].lvgSpecified = true;
                        _targetDiskParams[disk].lvg = Boolean.Parse(props[2]);
                     }
                  }
                  break;

               case "storage.targetdisk.gpt":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        _targetDiskParams[disk].gptSpecified = true;
                        _targetDiskParams[disk].gpt = Boolean.Parse(props[2]);
                     }
                  }
                  break;

               case "storage.targetdisk.datastore":
                  if (_targetType == TargetType.Managed && !String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        ConverterStorageParamsManagedTargetDiskParams diskParams =
                           (ConverterStorageParamsManagedTargetDiskParams)_targetDiskParams[disk];
                        diskParams.datastoreName = props[2];
                     }
                  }
                  break;

               case "storage.targetdisk.volumestoclone.count":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length) {
                        int count = Int32.Parse(props[2]);
                        _targetDiskParams[disk].volumesToClone =
                           new ConverterStorageParamsVolumeCloningParams[count];
                        for (int i = 0; i < count; ++i) {
                           _targetDiskParams[disk].volumesToClone[i] =
                              new ConverterStorageParamsVolumeCloningParams();
                        }
                     }
                  }
                  break;

               case "storage.targetdisk.volumestoclone.sourcevolumeid":
                  if (!String.IsNullOrEmpty(props[2])) {
                     int disk = Int32.Parse(props[1]);
                     int vol = Int32.Parse(props[2]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length &&
                         _targetDiskParams[disk].volumesToClone != null &&
                         vol < _targetDiskParams[disk].volumesToClone.Length) {
                        _targetDiskParams[disk].volumesToClone[vol].sourceVolumeId =
                           props[3];
                        // sourceVolumeId may contain '='
                        if (props.Length == 5) {
                           _targetDiskParams[disk].volumesToClone[vol].sourceVolumeId +=
                              "=" + props[4];
                        }
                     }
                  }
                  break;

               case "storage.targetdisk.volumestoclone.resize":
                  if (!String.IsNullOrEmpty(props[3])) {
                     int disk = Int32.Parse(props[1]);
                     int vol = Int32.Parse(props[2]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length &&
                         _targetDiskParams[disk].volumesToClone != null &&
                         vol < _targetDiskParams[disk].volumesToClone.Length) {
                        _targetDiskParams[disk].volumesToClone[vol].resizeSpecified = true;
                        _targetDiskParams[disk].volumesToClone[vol].resize =
                           Boolean.Parse(props[3]);
                     }
                  }
                  break;

               case "storage.targetdisk.volumestoclone.newcapacityinbytes":
                  if (!String.IsNullOrEmpty(props[3])) {
                     int disk = Int32.Parse(props[1]);
                     int vol = Int32.Parse(props[2]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length &&
                         _targetDiskParams[disk].volumesToClone != null &&
                         vol < _targetDiskParams[disk].volumesToClone.Length) {
                        _targetDiskParams[disk].volumesToClone[vol].newCapacityInBytesSpecified =
                           true;
                        _targetDiskParams[disk].volumesToClone[vol].newCapacityInBytes =
                           Int64.Parse(props[3]);
                     }
                  }
                  break;

               case "storage.targetdisk.volumestoclone.newclustersizeinbytes":
                  if (!String.IsNullOrEmpty(props[3])) {
                     int disk = Int32.Parse(props[1]);
                     int vol = Int32.Parse(props[2]);
                     if (_targetDiskParams != null && disk < _targetDiskParams.Length &&
                         _targetDiskParams[disk].volumesToClone != null &&
                         vol < _targetDiskParams[disk].volumesToClone.Length) {
                        _targetDiskParams[disk].volumesToClone[vol].newClusterSizeInBytesSpecified =
                           true;
                        _targetDiskParams[disk].volumesToClone[vol].newClusterSizeInBytes =
                           Int64.Parse(props[3]);
                     }
                  }
                  break;

               case "list.tasks":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _tasks = Int32.Parse(props[1]);
                  }
                  break;

               case "list.jobs":
                  if (!String.IsNullOrEmpty(props[1])) {
                     _jobs = Int32.Parse(props[1]);
                  }
                  break;

               default:
                  Debug.Fail("Unknown property: " + props[0]);
                  break;
            }
         }
      }

      /// <summary>
      /// Conversion operation.
      /// The most usual Converter operation - converts a source to a destination machine.
      /// May also install converter agent on the source machine.
      /// </summary>
      private void Convert()
      {
         if (_sourceType == SourceType.Physical &&
             _sourcePhysOsType == OsType.windowsOs &&
             _sourcePhysAgentPort > 0) {

            ConverterComputerSpecLiveComputerLocation liveComputerLocation =
               BuildLiveSourceLocation(_sourcePhysAddress, _sourcePhysUsername,
                                       _sourcePhysPassword, "", OsType.windowsOs,
                                       _sourcePhysThumbprint);

            _converterServer.InstallAgent(liveComputerLocation,
                                          _sourcePhysAgentPort,
                                          !_sourcePhysReboot);

            // Update source thumbprint with the one from agent install
            SetSourceThumbprint(Common.GetThumbprint(liveComputerLocation));
         }

         ConverterServerConversionConversionJobSpec jobSpec = BuildConversionJobSpec(false);
         Debug.Assert(jobSpec != null);

         ConverterServerConversionConversionJobInfo jobInfo = _converterServer.SubmitJob(jobSpec);

         if (jobInfo == null) {
            throw new Exception("Failed to submit Conversion Job.");
         }

         Console.WriteLine("Conversion Job Id = {0} created sucessfully.", jobInfo.key);
      }

      /// <summary>
      /// Reconfiguration operation.
      /// Reconfigures a virtual machine.
      /// </summary>
      private void Reconfig()
      {
         ConverterServerConversionConversionJobSpec jobSpec = BuildConversionJobSpec(true);
         Debug.Assert(jobSpec != null);

         ConverterServerConversionConversionJobInfo jobInfo = _converterServer.SubmitJob(jobSpec);

         if (jobInfo == null) {
            throw new Exception("Failed to submit Reconfig Job.");
         }

         Console.WriteLine("Conversion Job Id = {0} created sucessfully.", jobInfo.key);
      }

      /// <summary>
      /// Synchronization operation.
      /// Starts an incremental update of a synchronizable job.
      /// </summary>
      private void Synchronize()
      {
         // Reschedule or start an incremental task for a synchronizable Windows P2V job.
         ConverterServerConversionConversionParamsUpdateSpec newParams =
            new ConverterServerConversionConversionParamsUpdateSpec();
         newParams.synchronizationStartTimeSpecified = true;
         if (_jobSyncStart != DateTime.MinValue) {
            newParams.synchronizationStartTime = _jobSyncStart;
         } else {
            newParams.synchronizationStartTime = DateTime.Now;
         }
         newParams.doFinalizeSpecified = true;
         newParams.doFinalize = _jobSyncFinalize;

         ConverterConversionParams convParams = _converterServer.UpdateJob(_jobId, newParams);

         if (convParams == null) {
            throw new Exception("Failed to update Conversion Job.");
         }
      }

      /// <summary>
      /// Queries the source info and dumps it to the console.
      /// </summary>
      private void QuerySource()
      {
         ConverterComputerSpec sourceSpec = BuildSourceSpec();
         Debug.Assert(sourceSpec != null);

         ConverterComputerInfo info = _converterServer.QuerySource(sourceSpec);

         if (info == null) {
            throw new Exception("Failed to query source.");
         }

         String dump = Common.Dump(info);
         Console.WriteLine(dump);

         // Cache thumbprint
         SetSourceThumbprint(Common.GetThumbprint(sourceSpec.location));
      }

      /// <summary>
      /// Dumps various default cloning parameters.
      ///
      /// May fail the source against target location validation,
      /// in which case it will not return the defaults.
      /// </summary>
      private void GetDefaults()
      {
         ConverterComputerSpec sourceSpec = BuildSourceSpec();
         Debug.Assert(sourceSpec != null);
         ConverterTargetVmSpec targetSpec = BuildTargetVMSpec();
         Debug.Assert(targetSpec != null);

         ConverterDefaultConversionParamsResult result =
            _converterServer.ValidateTargetAndGetDefaults(sourceSpec, targetSpec);

         if (result == null) {
            throw new Exception("Failed to validate target and get defaults.");
         }

         String dump = Common.Dump(result);
         Console.WriteLine(dump);

         // Cache thumbprints
         SetSourceThumbprint(Common.GetThumbprint(sourceSpec.location));
         SetTargetThumbprint(Common.GetThumbprint(targetSpec.location));
      }

      /// <summary>
      /// Dumps the last tasks and jobs to the console
      /// </summary>
      private void List()
      {
         String dump;

         if (_tasks > 0) {
            ConverterTaskInfo[] taskInfo = _converterServer.GetTaskHistory(_tasks);

            if (taskInfo == null) {
               throw new Exception("Failed to get tasks history.");
            }

            dump = Common.Dump(taskInfo);
            Console.WriteLine(dump);
         }

         if (_jobs > 0) {
            ConverterServerConversionConversionJobInfo[] jobInfo =
               _converterServer.GetJobHistory(_jobs);

            if (jobInfo == null) {
               throw new Exception("Failed to get jobs history.");
            }

            dump = Common.Dump(jobInfo);
            Console.WriteLine(dump);
         }
      }

      /// <summary>
      /// Creates a computer location object for a live (powered on) source machine
      /// Converter treats any live machine as physical though it might be a
      /// powered on virtual machine.
      /// </summary>
      /// <param name="sourceName">Name or IP address of the machine</param>
      /// <param name="sourceUsername">Administrative user name to log in</param>
      /// <param name="sourcePassword">Password to log in or passphrase if sourcePhysKeyPath
      /// is not empty</param>
      /// <param name="sourcePhysKeyPath">Path to client private key file when key-based
      /// authentication is used</param>
      /// <param name="osType">OS type - Windows or Linux</param>
      /// <param name="sslThumbprint">Optional SSL thumbprint of converter agent
      /// (for Windows machines)</param>
      /// <returns>The location object</returns>
      private static ConverterComputerSpecLiveComputerLocation
      BuildLiveSourceLocation(String sourceName, String sourceUsername, String sourcePassword,
                              String sourcePhysKeyPath,
                              OsType osType, String sslThumbprint)
      {
         ConverterComputerSpecLiveComputerLocation liveSourceLocation =
            new ConverterComputerSpecLiveComputerLocation();
         liveSourceLocation.hostname = sourceName;
         liveSourceLocation.username = sourceUsername;
         liveSourceLocation.password = sourcePassword;
         if (!String.IsNullOrEmpty(sourcePhysKeyPath)) {
            StreamReader reader = new StreamReader(sourcePhysKeyPath);
            liveSourceLocation.clientPrivateKey = reader.ReadToEnd();
         }
         liveSourceLocation.osType = osType.ToString();
         liveSourceLocation.verifyPeerSpecified = false; // Do not specify
         liveSourceLocation.sslThumbprint = sslThumbprint;

         return liveSourceLocation;
      }

      /// <summary>
      /// Creates a object for connection to vSphere host (ESXi or VCenter server)
      /// </summary>
      /// <param name="hostname">Name or IP address of the host</param>
      /// <param name="username">User name to log in</param>
      /// <param name="password">Password to log in</param>
      /// <param name="thumbprint">Optional SSL thumbprint</param>
      /// <returns></returns>
      private static ConverterVimConnectionSpec
      BuildVimConnectionSpec(String hostname, String username, String password, String thumbprint)
      {
         ConverterVimConnectionSpecLoginVimCredentials vimCredentials =
            new ConverterVimConnectionSpecLoginVimCredentials();
         vimCredentials.password = password;
         vimCredentials.username = username;

         ConverterVimConnectionSpec vimConnectionSpec = new ConverterVimConnectionSpec();
         vimConnectionSpec.hostname = hostname;
         vimConnectionSpec.credentials = vimCredentials;
         vimConnectionSpec.verifyPeerSpecified = false; // Do not specify
         vimConnectionSpec.sslThumbprint = thumbprint;

         return vimConnectionSpec;
      }

      /// <summary>
      /// Creates a spec object for conversion.
      /// This is the object with which conversion can be started
      /// </summary>
      /// <param name="reconfigOnly">True if the spec is only for reconfiguration</param>
      /// <returns>The spec object</returns>
      private ConverterServerConversionConversionJobSpec BuildConversionJobSpec(bool reconfigOnly)
      {
         ConverterServerConversionConversionJobSpec jobSpec =
                                 new ConverterServerConversionConversionJobSpec();
         jobSpec.name = reconfigOnly ? "Reconfig" : "Convert";
         jobSpec.firstRunSpecified = false; // Do not specify
         jobSpec.startSuspendedSpecified = false;
         jobSpec.source = BuildSourceSpec();
         jobSpec.conversionParams = BuildConversionParams(reconfigOnly);
         jobSpec.p2vSourceModificationSpec = new ConverterServerConversionP2VSourceModificationSpec();

         // Synchronization is applicable only to Windows P2V
         if (_sourceType == SourceType.Physical && _sourcePhysOsType == OsType.windowsOs) {
            jobSpec.synchronizeImmediatelySpecified = _jobSync;
            jobSpec.synchronizationStartTimeSpecified = false;
            jobSpec.synchronizeImmediately = false;

            if (_jobSync) {
               if (_jobSyncStart != DateTime.MinValue) {
                  jobSpec.synchronizationStartTimeSpecified = true;
                  jobSpec.synchronizationStartTime = _jobSyncStart;
               } else {
                  jobSpec.synchronizeImmediately = true;
               }
            }
         }

         return jobSpec;
      }

      /// <summary>
      /// Create a computer spec object for the source of conversion
      /// reconfiguration, query, or getting defaults.
      /// </summary>
      /// <returns></returns>
      private ConverterComputerSpec BuildSourceSpec()
      {
         ConverterComputerSpec sourceSpec = new ConverterComputerSpec();

         switch (_sourceType) {
            case SourceType.Physical:
               sourceSpec.location =
                  BuildLiveSourceLocation(_sourcePhysAddress,
                                          _sourcePhysUsername,
                                          _sourcePhysPassword,
                                          _sourcePhysKeyPath,
                                          _sourcePhysOsType,
                                          _sourcePhysThumbprint);
               break;
            case SourceType.Managed:
               sourceSpec.location = BuildManagedSourceLocation();
               break;
            case SourceType.Hosted:
               sourceSpec.location = BuildHostedSourceLocation();
               break;
            case SourceType.HyperV:
               sourceSpec.location = BuildHypervSourceLocation();
               break;
            default:
               Debug.Fail("Invalid source type");
               break;
         }

         return sourceSpec;
      }

      /// <summary>
      /// Create an object with conversion parameters.
      /// This is the most essential part of a conversion job.
      /// </summary>
      /// <param name="reconfigOnly">True if the spec is only for reconfiguration</param>
      /// <returns></returns>
      private ConverterConversionParams BuildConversionParams(bool reconfigOnly)
      {
         ConverterConversionParams conversionParams = new ConverterConversionParams();

         conversionParams.doCloneSpecified = true;
         if (reconfigOnly) {
            conversionParams.doClone = false;
         } else {
            conversionParams.doClone = true;
            conversionParams.cloningParams = BuildCloningParams();
         }
         conversionParams.doReconfigSpecified = true;
         conversionParams.doReconfig = true;
         // Possible addition:
         // Create reconfigiration parameters for changing service states
         conversionParams.reconfigParams = null;

         // For installing VMware Tools
         // Applicable to Windows only; check reconfiguration capabilities
         conversionParams.doInstallToolsSpecified = true;
         conversionParams.doInstallTools = false;

         // Possible addition:
         // Create conversionParams.customizationParams.specification
         // to customize the resulting VM
         // Applicable to Windows only; check reconfiguration capabilities
         conversionParams.doCustomizeSpecified = true;
         conversionParams.doCustomize = false;
         conversionParams.customizationParams = null;

         conversionParams.doUninstallAgentSpecified = false;
         conversionParams.powerOnTargetVMSpecified = false;
         conversionParams.removeRestoreCheckpointsSpecified = false;

         // Applicable only to Linux P2V
         if (_sourceType == SourceType.Physical && _sourcePhysOsType == OsType.linuxOs) {
            // Possible addition:
            // Create helper network parameters to change default helper network setting
            conversionParams.helperVmNetworkParams = null;
         }

         // Possible addition:
         // Create throttling params to throttle the cloning
         conversionParams.throttlingParams = null;

         return conversionParams;
      }


      /// <summary>
      /// Create an object with cloning parameters.
      /// Part of conversion parameters.
      /// </summary>
      /// <returns></returns>
      private ConverterCloningParams BuildCloningParams()
      {
         ConverterCloningParams cloningParams = new ConverterCloningParams();

         cloningParams.target = BuildTargetVMSpec();
         cloningParams.storageParams = BuildStorageParams();
         cloningParams.basicHardwareParams = BuildBasicHardwareParams();
         // Possible addition:
         // Set to customize network settings
         cloningParams.networkParams = null;

         // Synchronization settings are applicable only to Windows P2V
         if (_sourceType == SourceType.Physical && _sourcePhysOsType == OsType.windowsOs) {
            cloningParams.doSynchronizeSpecified = true;
            cloningParams.doSynchronize = _jobSync;

            if (_jobSync) {
               cloningParams.doFinalizeSpecified = true;
               cloningParams.doFinalize = _jobSyncFinalize;
            } else {
               cloningParams.doFinalizeSpecified = false;
            }

            // Possible addition:
            // Set to stop services before final sync
            cloningParams.servicesToSuspend = null;
         }

         return cloningParams;
      }

      /// <summary>
      /// Create an object with hardware parameters.
      /// Part of cloning parameters.
      /// </summary>
      /// <returns></returns>
      private ConverterBasicHardwareParams BuildBasicHardwareParams()
      {
         ConverterBasicHardwareParams hardwareParams = new ConverterBasicHardwareParams();

         if (_vmVcpu > 0) {
            hardwareParams.numCPUs = _vmVcpu;
            hardwareParams.numCPUsSpecified = true;
         }

         if (_coresPerSocket > 0) {
            hardwareParams.numCoresPerSocket = _coresPerSocket;
            hardwareParams.numCoresPerSocketSpecified = true;
         }

         if (_vmMemory > 0) {
            hardwareParams.memoryMB = _vmMemory;
            hardwareParams.memoryMBSpecified = true;
         }

         return hardwareParams;
      }

      /// <summary>
      /// Create an object with storage parameters.
      /// Part of cloning parameters.
      /// </summary>
      /// <returns></returns>
      private ConverterStorageParams BuildStorageParams()
      {
         ConverterStorageParams converterStorageParams = new ConverterStorageParams();
         converterStorageParams.cloningMode = _cloningMode.ToString();
         if (!String.IsNullOrEmpty(_diskControllerType)) {
            converterStorageParams.diskControllerType = _diskControllerType;
         }
         converterStorageParams.targetDiskParams = _targetDiskParams;
         converterStorageParams.optimizedPartitionAlignmentSpecified = true;
         converterStorageParams.optimizedPartitionAlignment = _optimizeAlignment;

         return converterStorageParams;
      }

      /// <summary>
      /// Create an object specifying the destination VM.
      /// Part of cloning parameters.
      /// </summary>
      /// <returns></returns>
      private ConverterTargetVmSpec BuildTargetVMSpec()
      {
         ConverterTargetVmSpec targetVMSpec = new ConverterTargetVmSpec();
         targetVMSpec.name = _vmName;

         switch (_targetType) {
            case TargetType.Managed:
               ConverterTargetVmSpecManagedVmLocation managedVMLocation =
                  new ConverterTargetVmSpecManagedVmLocation();
               managedVMLocation.vimConnect =
                  BuildVimConnectionSpec(_targetManagedAddress,
                                         _targetManagedUsername,
                                         _targetManagedPassword,
                                         _targetManagedThumbprint);
               if (!String.IsNullOrEmpty(_targetManagedHost)) {
                  ManagedObjectReference mor = new ManagedObjectReference();
                  mor.type = "HostSystem";
                  mor.Value = _targetManagedHost;
                  managedVMLocation.host = mor;
               }
               if (!String.IsNullOrEmpty(_targetManagedResourcePool)) {
                  ManagedObjectReference mor = new ManagedObjectReference();
                  mor.type = "ResourcePool";
                  mor.Value = _targetManagedResourcePool;
                  managedVMLocation.resourcePool = mor;
               }
               if (!String.IsNullOrEmpty(_targetManagedCluster)) {
                  ManagedObjectReference mor = new ManagedObjectReference();
                  mor.type = "ClusterComputeResource";
                  mor.Value = _targetManagedCluster;
                  managedVMLocation.computeResource = mor;
               }
               if (!String.IsNullOrEmpty(_targetManagedVmFolder)) {
                  ManagedObjectReference mor = new ManagedObjectReference();
                  mor.type = "Folder";
                  mor.Value = _targetManagedVmFolder;
                  managedVMLocation.vmFolder = mor;
               }
               targetVMSpec.location = managedVMLocation;
               // Possible addition:
               // Set for using a specific hardware version
               targetVMSpec.hardwareVersion = null; // Use target default
               break;

            case TargetType.Hosted:
               ConverterTargetVmSpecHostedVmLocation hostedVMLocation =
                  new ConverterTargetVmSpecHostedVmLocation();
               hostedVMLocation.directory = _targetHostedDirectory;
               if (!String.IsNullOrEmpty(_targetHostedNetworkUsername)) {
                  hostedVMLocation.networkUsername = _targetHostedNetworkUsername;
               }
               if (!String.IsNullOrEmpty(_targetHostedNetworkPassword)) {
                  hostedVMLocation.networkPassword = _targetHostedNetworkPassword;
               }
               targetVMSpec.location = hostedVMLocation;
               // Change for other product versions.
               // Currently supported:
               // workstation10x, workstation11x,
               // player6x, player7x,
               // fusion6x, fusion7x,
               targetVMSpec.productVersion = "workstation11x";
               break;

            default:
               Debug.Fail("Invalid target type");
               break;
         }

         return targetVMSpec;
      }

      /// <summary>
      /// Create an object specifying a powered off managed VMware (ESX or vCenter server)
      /// virtual source machine.
      /// </summary>
      /// <returns></returns>
      private ConverterComputerSpecManagedVmLocation BuildManagedSourceLocation()
      {
         ConverterComputerSpecManagedVmLocation managedVmLocation =
               new ConverterComputerSpecManagedVmLocation();
         managedVmLocation.vimConnect = BuildVimConnectionSpec(_sourceManagedAddress,
                                                               _sourceManagedUsername,
                                                               _sourceManagedPassword,
                                                               _sourceManagedThumbprint);

         ManagedObjectReference morVm = new ManagedObjectReference();
         morVm.type = "VirtualMachine";
         morVm.Value = _sourceManagedVm;

         managedVmLocation.vm = morVm;

         return managedVmLocation;
      }

      /// <summary>
      /// Create an object specifying a hosted source machine. It can be one of:
      ///   - powered off hosted VMware (Workstation, Player, Fusion)
      ///     virtual source machine
      ///   - VMware backup
      ///   - third party image
      /// </summary>
      /// <returns></returns>
      private ConverterComputerSpecHostedVmLocation BuildHostedSourceLocation()
      {
         ConverterComputerSpecHostedVmLocation hostedVmLocation = null;

         if (_sourceHostedType == "vmwareVM") {
            // VNware hosted VM
            hostedVmLocation = new ConverterComputerSpecVMwareHostedVmLocation();
         } else {
            // Third party image or VMware backup
            hostedVmLocation = new ConverterComputerSpecHostedVmLocation();
         }

         hostedVmLocation.type = _sourceHostedType;
         hostedVmLocation.configFilePath = _sourceHostedConfigFilePath;
         if (!String.IsNullOrEmpty(_sourceHostedPassword)) {
            hostedVmLocation.password = _sourceHostedPassword;
         }
         if (!String.IsNullOrEmpty(_sourceHostedNetworkUsername)) {
            hostedVmLocation.networkUsername = _sourceHostedNetworkUsername;
         }
         if (!String.IsNullOrEmpty(_sourceHostedNetworkPassword)) {
            hostedVmLocation.networkPassword = _sourceHostedNetworkPassword;
         }

         return hostedVmLocation;
      }

      /// <summary>
      /// Create an object specifying a powered off Hyper-V virtual source machine.
      /// </summary>
      /// <returns></returns>
      private ConverterComputerSpecHyperVComputerLocation BuildHypervSourceLocation()
      {
         ConverterComputerSpecHyperVComputerLocation hyperVLocation =
               new ConverterComputerSpecHyperVComputerLocation();

         hyperVLocation.hostname = _sourceHypervHostname;
         if (_sourceHypervPort > 0) {
            hyperVLocation.portSpecified = true;
            hyperVLocation.port = _sourceHypervPort;
         }
         if (!String.IsNullOrEmpty(_sourceHypervUsername)) {
            hyperVLocation.username = _sourceHypervUsername;
         }
         if (!String.IsNullOrEmpty(_sourceHypervPassword)) {
            hyperVLocation.password = _sourceHypervPassword;
         }
         if (!String.IsNullOrEmpty(_sourceHypervVmName)) {
            hyperVLocation.vmName = _sourceHypervVmName;
         }
         hyperVLocation.vmGUID = _sourceHypervVmGuid;
         hyperVLocation.verifyPeerSpecified = false; // Do not specify
         hyperVLocation.sslThumbprint = _sourceHypervThumbprint;

         return hyperVLocation;
      }

      /// <summary>
      /// Cache the source machine SSL thumbprint
      /// </summary>
      /// <param name="thumbprint"></param>
      private void SetSourceThumbprint(String thumbprint)
      {
         // Cache thumbprint from source
         switch (_sourceType) {
            case SourceType.Physical:
               _sourcePhysThumbprint = thumbprint;
               break;

            case SourceType.Managed:
               _sourceManagedThumbprint = thumbprint;
               break;

            case SourceType.HyperV:
               _sourceHypervThumbprint = thumbprint;
               break;
         }
      }

      /// <summary>
      /// Cache the destination host SSL thumbprint
      /// </summary>
      /// <param name="thumbprint"></param>
      private void SetTargetThumbprint(String thumbprint)
      {
         // Cache thumbprint from destination.
         switch (_targetType) {
            case TargetType.Managed:
               _targetManagedThumbprint = thumbprint;
               break;
         }
      }
   }
}
