// Copyright (c) 2012, Outercurve Foundation.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// - Redistributions of source code must  retain  the  above copyright notice, this
//   list of conditions and the following disclaimer.
//
// - Redistributions in binary form  must  reproduce the  above  copyright  notice,
//   this list of conditions  and  the  following  disclaimer in  the documentation
//   and/or other materials provided with the distribution.
//
// - Neither  the  name  of  the  Outercurve Foundation  nor   the   names  of  its
//   contributors may be used to endorse or  promote  products  derived  from  this
//   software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT  NOT  LIMITED TO, THE IMPLIED
// WARRANTIES  OF  MERCHANTABILITY   AND  FITNESS  FOR  A  PARTICULAR  PURPOSE  ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL,  SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO,  PROCUREMENT  OF  SUBSTITUTE  GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)  HOWEVER  CAUSED AND ON
// ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT  LIABILITY,  OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING  IN  ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Data;
using System.Xml;
using WebsitePanel.Providers.Common;
using WebsitePanel.Providers.ResultObjects;
using WebsitePanel.Providers.Virtualization;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using WebsitePanel.Providers;
using System.Text;
using System.Collections;
using System.Net.Mail;

namespace WebsitePanel.EnterpriseServer
{
    public class VirtualizationServerController
    {
        private const string SHUTDOWN_REASON = "WebsitePanel - Initiated by user";
        private const string SHUTDOWN_REASON_CHANGE_CONFIG = "WebsitePanel - changing VPS configuration";
        private const Int64 Size1G = 0x40000000;
        private const string MS_MAC_PREFIX = "00155D"; // IEEE prefix of MS MAC addresses

        // default server creation (if "Unlimited" was specified in the hosting plan)
        private const int DEFAULT_PASSWORD_LENGTH = 12;
        private const int DEFAULT_RAM_SIZE = 512; // megabytes
        private const int DEFAULT_HDD_SIZE = 20; // gigabytes
        private const int DEFAULT_PRIVATE_IPS_NUMBER = 1;
        private const int DEFAULT_SNAPSHOTS_NUMBER = 5;

        #region Virtual Machines
        public static VirtualMachineMetaItemsPaged GetVirtualMachines(int packageId,
            string filterColumn, string filterValue, string sortColumn, int startRow, int maximumRows, bool recursive)
        {
            VirtualMachineMetaItemsPaged result = new VirtualMachineMetaItemsPaged();

            // get reader
            IDataReader reader = DataProvider.GetVirtualMachinesPaged(
                    SecurityContext.User.UserId,
                    packageId, filterColumn, filterValue, sortColumn, startRow, maximumRows, recursive);

            // number of items = first data reader
            reader.Read();
            result.Count = (int)reader[0];

            // items = second data reader
            reader.NextResult();
            result.Items = ObjectUtils.CreateListFromDataReader<VirtualMachineMetaItem>(reader).ToArray();

            return result;
        }

        public static VirtualMachine[] GetVirtualMachinesByServiceId(int serviceId)
        {
            // get proxy
            VirtualizationServer vps = GetVirtualizationProxy(serviceId);

            // load details
            return vps.GetVirtualMachines();
        }
        #endregion

        #region Private Network
        public static PrivateIPAddressesPaged GetPackagePrivateIPAddressesPaged(int packageId,
            string filterColumn, string filterValue, string sortColumn, int startRow, int maximumRows)
        {
            PrivateIPAddressesPaged result = new PrivateIPAddressesPaged();

            // get reader
            IDataReader reader = DataProvider.GetPackagePrivateIPAddressesPaged(packageId, filterColumn, filterValue,
                sortColumn, startRow, maximumRows);

            // number of items = first data reader
            reader.Read();
            result.Count = (int)reader[0];

            // items = second data reader
            reader.NextResult();
            result.Items = ObjectUtils.CreateListFromDataReader<PrivateIPAddress>(reader).ToArray();

            return result;
        }

        public static List<PrivateIPAddress> GetPackagePrivateIPAddresses(int packageId)
        {
            return ObjectUtils.CreateListFromDataReader<PrivateIPAddress>(
                DataProvider.GetPackagePrivateIPAddresses(packageId));
        }
        #endregion

        #region User Permissions
        public static List<VirtualMachinePermission> GetSpaceUserPermissions(int packageId)
        {
            List<VirtualMachinePermission> result = new List<VirtualMachinePermission>();
            return result;
        }

        public static int UpdateSpaceUserPermissions(int packageId, VirtualMachinePermission[] permissions)
        {
            // VDC - UPDATE_PERMISSIONS
            return 0;
        }
        #endregion

        #region Audit Log
        public static List<LogRecord> GetSpaceAuditLog(int packageId, DateTime startPeriod, DateTime endPeriod,
            int severity, string sortColumn, int startRow, int maximumRows)
        {
            List<LogRecord> result = new List<LogRecord>();
            return result;
        }

        public static List<LogRecord> GetVirtualMachineAuditLog(int itemId, DateTime startPeriod, DateTime endPeriod,
            int severity, string sortColumn, int startRow, int maximumRows)
        {
            List<LogRecord> result = new List<LogRecord>();
            return result;
        }
        #endregion

        #region VPS Create – Name & OS
        public static LibraryItem[] GetOperatingSystemTemplates(int packageId)
        {
            // load service settings
            int serviceId = GetServiceId(packageId);

            // return templates
            return GetOperatingSystemTemplatesByServiceId(serviceId);
        }

        public static LibraryItem[] GetOperatingSystemTemplatesByServiceId(int serviceId)
        {
            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(serviceId);
            string path = settings["OsTemplatesPath"];

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(serviceId);

            return vs.GetLibraryItems(path);
        }
        #endregion

        #region VPS Create - Configuration
        public static int GetMaximumCpuCoresNumber(int packageId)
        {
            // get proxy
            VirtualizationServer vs = GetVirtualizationProxyByPackageId(packageId);

            return vs.GetProcessorCoresNumber();
        }

        public static string GetDefaultExportPath(int itemId)
        {
            // load meta item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            if (vm == null)
                return null;

            // load settings
            StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);
            return settings["ExportedVpsPath"];
        }
        #endregion

        #region VPS Create
        public static IntResult CreateDefaultVirtualMachine(int packageId,
            string hostname, string osTemplate, string password, string summaryLetterEmail)
        {
            if (String.IsNullOrEmpty(osTemplate))
                throw new ArgumentNullException("osTemplate");

            IntResult res = new IntResult();

            // load package info
            PackageInfo package = PackageController.GetPackage(packageId);
            if (package == null)
            {
                res.ErrorCodes.Add("VPS_CREATE_PACKAGE_NOT_FOUND");
                return res;
            }

            // generate host name if not specified
            if (String.IsNullOrEmpty(hostname))
            {
                // load hostname pattern
                PackageSettings spaceSettings = PackageController.GetPackageSettings(packageId, PackageSettings.VIRTUAL_PRIVATE_SERVERS);
                string hostnamePattern = spaceSettings["HostnamePattern"];
                if (String.IsNullOrEmpty(hostnamePattern))
                {
                    res.ErrorCodes.Add("VPS_CREATE_EMPTY_HOSTNAME_PATTERN");
                    return res;
                }

                hostname = EvaluateSpaceVariables(hostnamePattern, packageId);
            }

            // generate password if not specified
            if (String.IsNullOrEmpty(password))
            {
                int passwordLength = DEFAULT_PASSWORD_LENGTH; // default length

                // load password policy
                UserSettings userSettings = UserController.GetUserSettings(package.UserId, UserSettings.VPS_POLICY);
                string passwordPolicy = userSettings["AdministratorPasswordPolicy"];

                if (!String.IsNullOrEmpty(passwordPolicy))
                {
                    // get second parameter - max length
                    passwordLength = Utils.ParseInt(passwordPolicy.Split(';')[1].Trim(), passwordLength);
                }

                // generate password
                password = Utils.GetRandomString(passwordLength);
            }

            // load quotas
            PackageContext cntx = PackageController.GetPackageContext(packageId);
            if (cntx.Groups.ContainsKey(ResourceGroups.VPS))
            {
                res.ErrorCodes.Add("VPS_CREATE_VPS_GROUP_DISABLED");
                return res;
            }

            // CPU cores
            int cpuCores = cntx.Quotas[Quotas.VPS_CPU_NUMBER].QuotaAllocatedValue;
            if (cpuCores == -1) // unlimited is not possible
                cpuCores = GetMaximumCpuCoresNumber(packageId);

            // RAM
            int ramMB = cntx.Quotas[Quotas.VPS_RAM].QuotaAllocatedValue;
            if (ramMB == -1) // unlimited is not possible
                ramMB = DEFAULT_RAM_SIZE;

            // HDD
            int hddGB = cntx.Quotas[Quotas.VPS_HDD].QuotaAllocatedValue;
            if (hddGB == -1) // unlimited is not possible
                hddGB = DEFAULT_HDD_SIZE;

            // snapshots
            int snapshots = cntx.Quotas[Quotas.VPS_SNAPSHOTS_NUMBER].QuotaAllocatedValue;
            if (snapshots == -1) // unlimited is not possible
                snapshots = DEFAULT_SNAPSHOTS_NUMBER;

            bool dvdInstalled = !cntx.Quotas[Quotas.VPS_DVD_ENABLED].QuotaExhausted;
            bool bootFromCD = !cntx.Quotas[Quotas.VPS_BOOT_CD_ENABLED].QuotaExhausted;
            bool numLock = true;

            bool startShutdownAllowed = !cntx.Quotas[Quotas.VPS_START_SHUTDOWN_ALLOWED].QuotaExhausted;
            bool pauseResumeAllowed = !cntx.Quotas[Quotas.VPS_PAUSE_RESUME_ALLOWED].QuotaExhausted;
            bool rebootAllowed = !cntx.Quotas[Quotas.VPS_REBOOT_ALLOWED].QuotaExhausted;
            bool resetAllowed = !cntx.Quotas[Quotas.VPS_RESET_ALOWED].QuotaExhausted;
            bool reinstallAllowed = !cntx.Quotas[Quotas.VPS_REINSTALL_ALLOWED].QuotaExhausted;

            bool externalNetworkEnabled = !cntx.Quotas[Quotas.VPS_EXTERNAL_NETWORK_ENABLED].QuotaExhausted;
            int externalAddressesNumber = cntx.Quotas[Quotas.VPS_EXTERNAL_IP_ADDRESSES_NUMBER].QuotaAllocatedValue;
            bool randomExternalAddresses = true;
            int[] externalAddresses = new int[0]; // empty array
            if (externalNetworkEnabled)
            {
                int maxExternalAddresses = ServerController.GetPackageUnassignedIPAddresses(packageId, IPAddressPool.VpsExternalNetwork).Count;
                if (externalAddressesNumber == -1
                    || externalAddressesNumber > maxExternalAddresses)
                    externalAddressesNumber = maxExternalAddresses;
            }

            bool privateNetworkEnabled = !cntx.Quotas[Quotas.VPS_PRIVATE_NETWORK_ENABLED].QuotaExhausted;
            int privateAddressesNumber = cntx.Quotas[Quotas.VPS_PRIVATE_IP_ADDRESSES_NUMBER].QuotaAllocatedValue;
            bool randomPrivateAddresses = true;
            string[] privateAddresses = new string[0]; // empty array
            if (privateAddressesNumber == -1) // unlimited is not possible
            {
                privateAddressesNumber = DEFAULT_PRIVATE_IPS_NUMBER;
            }

            // create server and return result
            return CreateVirtualMachine(packageId, hostname, osTemplate, password, summaryLetterEmail,
                cpuCores, ramMB, hddGB, snapshots,
                dvdInstalled, bootFromCD, numLock,
                startShutdownAllowed, pauseResumeAllowed, rebootAllowed, resetAllowed, reinstallAllowed,
                externalNetworkEnabled, externalAddressesNumber, randomExternalAddresses, externalAddresses,
                privateNetworkEnabled, privateAddressesNumber, randomPrivateAddresses, privateAddresses);
        }

        public static IntResult CreateVirtualMachine(int packageId,
                string hostname, string osTemplateFile, string password, string summaryLetterEmail,
                int cpuCores, int ramMB, int hddGB, int snapshots,
                bool dvdInstalled, bool bootFromCD, bool numLock,
                bool startShutdownAllowed, bool pauseResumeAllowed, bool rebootAllowed, bool resetAllowed, bool reinstallAllowed,
                bool externalNetworkEnabled, int externalAddressesNumber, bool randomExternalAddresses, int[] externalAddresses,
                bool privateNetworkEnabled, int privateAddressesNumber, bool randomPrivateAddresses, string[] privateAddresses)
        {
            // result object
            IntResult res = new IntResult();

            // meta item
            VirtualMachine vm = null;

            try
            {
                #region Check account and space statuses
                // check account
                if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                    return res;

                // check package
                if (!SecurityContext.CheckPackage(res, packageId, DemandPackage.IsActive))
                    return res;
                
                #endregion

                #region Check if host name is already used

                try
                {
                    ServiceProviderItem item = PackageController.GetPackageItemByName(packageId, hostname,
                                                                                      typeof (VirtualMachine));
                    if (item != null)
                    {
                        res.ErrorCodes.Add(VirtualizationErrorCodes.HOST_NAMER_IS_ALREADY_USED);
                        return res;
                    }
                }
                catch(Exception ex)
                {
                    res.AddError(VirtualizationErrorCodes.CANNOT_CHECK_HOST_EXISTS, ex);
                    return res;   
                }

                #endregion

                #region Check Quotas
                // check quotas
                List<string> quotaResults = new List<string>();
                PackageContext cntx = PackageController.GetPackageContext(packageId);

                CheckListsQuota(cntx, quotaResults, Quotas.VPS_SERVERS_NUMBER, VirtualizationErrorCodes.QUOTA_EXCEEDED_SERVERS_NUMBER);

                CheckNumericQuota(cntx, quotaResults, Quotas.VPS_CPU_NUMBER, cpuCores, VirtualizationErrorCodes.QUOTA_EXCEEDED_CPU);
                CheckNumericQuota(cntx, quotaResults, Quotas.VPS_RAM, ramMB, VirtualizationErrorCodes.QUOTA_EXCEEDED_RAM);
                CheckNumericQuota(cntx, quotaResults, Quotas.VPS_HDD, hddGB, VirtualizationErrorCodes.QUOTA_EXCEEDED_HDD);
                CheckNumericQuota(cntx, quotaResults, Quotas.VPS_SNAPSHOTS_NUMBER, snapshots, VirtualizationErrorCodes.QUOTA_EXCEEDED_SNAPSHOTS);

                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_DVD_ENABLED, dvdInstalled, VirtualizationErrorCodes.QUOTA_EXCEEDED_DVD_ENABLED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_BOOT_CD_ALLOWED, bootFromCD, VirtualizationErrorCodes.QUOTA_EXCEEDED_CD_ALLOWED);

                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_START_SHUTDOWN_ALLOWED, startShutdownAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_START_SHUTDOWN_ALLOWED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_PAUSE_RESUME_ALLOWED, pauseResumeAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_PAUSE_RESUME_ALLOWED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_REBOOT_ALLOWED, rebootAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_REBOOT_ALLOWED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_RESET_ALOWED, resetAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_RESET_ALOWED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_REINSTALL_ALLOWED, reinstallAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_REINSTALL_ALLOWED);

                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_EXTERNAL_NETWORK_ENABLED, externalNetworkEnabled, VirtualizationErrorCodes.QUOTA_EXCEEDED_EXTERNAL_NETWORK_ENABLED);
                CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_PRIVATE_NETWORK_ENABLED, privateNetworkEnabled, VirtualizationErrorCodes.QUOTA_EXCEEDED_PRIVATE_NETWORK_ENABLED);

                // check external addresses number
                if (!randomExternalAddresses && externalAddresses != null)
                    externalAddressesNumber = externalAddresses.Length;

                int maxAddresses = ServerController.GetPackageUnassignedIPAddresses(packageId, IPAddressPool.VpsExternalNetwork).Count;
                if (externalNetworkEnabled && externalAddressesNumber > maxAddresses)
                    quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_EXTERNAL_ADDRESSES_NUMBER + ":" + maxAddresses.ToString());

                // check private addresses number
                if (!randomPrivateAddresses && privateAddresses != null)
                    privateAddressesNumber = privateAddresses.Length;
                CheckNumericQuota(cntx, quotaResults, Quotas.VPS_PRIVATE_IP_ADDRESSES_NUMBER, privateAddressesNumber, VirtualizationErrorCodes.QUOTA_EXCEEDED_PRIVATE_ADDRESSES_NUMBER);

                // check management network parameters
                NetworkAdapterDetails manageNic = GetManagementNetworkDetails(packageId);
                if (!String.IsNullOrEmpty(manageNic.NetworkId))
                {
                    // network enabled - check management IPs pool
                    int manageIpsNumber = ServerController.GetUnallottedIPAddresses(
                            packageId, ResourceGroups.VPS, IPAddressPool.VpsManagementNetwork).Count;

                    if (manageIpsNumber == 0)
                        quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_MANAGEMENT_NETWORK);
                }

                // check acceptable values
                if (ramMB <= 0)
                    quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_RAM);
                if (hddGB <= 0)
                    quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_HDD);
                if (snapshots < 0)
                    quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_SNAPSHOTS);

                if (quotaResults.Count > 0)
                {
                    res.ErrorCodes.AddRange(quotaResults);
                    return res;
                }
                #endregion

                #region Check input parameters
                // check private network IP addresses if they are specified
                List<string> checkResults = CheckPrivateIPAddresses(packageId, privateAddresses);
                if (checkResults.Count > 0)
                {
                    res.ErrorCodes.AddRange(checkResults);
                    return res;
                }
                #endregion

                #region Context variables
                // service ID
                int serviceId = GetServiceId(packageId);

                // load service settings
                StringDictionary settings = ServerController.GetServiceSettings(serviceId);
                #endregion

                #region Create meta item
                // create meta item
                vm = new VirtualMachine();

                vm.Name = hostname;
                vm.AdministratorPassword = CryptoUtils.Encrypt(password);
                vm.PackageId = packageId;
                vm.VirtualMachineId = null; // from service
                vm.ServiceId = serviceId;

                vm.CurrentTaskId = Guid.NewGuid().ToString("N"); // generate creation task id
                vm.ProvisioningStatus = VirtualMachineProvisioningStatus.InProgress;

                vm.CpuCores = cpuCores;
                vm.RamSize = ramMB;
                vm.HddSize = hddGB;
                vm.SnapshotsNumber = snapshots;
                vm.DvdDriveInstalled = dvdInstalled;
                vm.BootFromCD = bootFromCD;
                vm.NumLockEnabled = numLock;
                vm.StartTurnOffAllowed = startShutdownAllowed;
                vm.PauseResumeAllowed = pauseResumeAllowed;
                vm.RebootAllowed = rebootAllowed;
                vm.ResetAllowed = resetAllowed;
                vm.ReinstallAllowed = reinstallAllowed;

                // networking
                vm.ExternalNetworkEnabled = externalNetworkEnabled;
                vm.PrivateNetworkEnabled = privateNetworkEnabled;
                vm.ManagementNetworkEnabled = !String.IsNullOrEmpty(manageNic.NetworkId);

                // load OS templates
                LibraryItem osTemplate = null;

                try
                {
                    LibraryItem[] osTemplates = GetOperatingSystemTemplates(vm.PackageId);
                    foreach (LibraryItem item in osTemplates)
                    {
                        if (String.Compare(item.Path, osTemplateFile, true) == 0)
                        {
                            osTemplate = item;

                            // check minimal disk size
                            if (osTemplate.DiskSize > 0 && vm.HddSize < osTemplate.DiskSize)
                            {
                                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.QUOTA_TEMPLATE_DISK_MINIMAL_SIZE + ":" + osTemplate.DiskSize);
                                return res;
                            }

                            vm.OperatingSystemTemplate = osTemplate.Name;
                            vm.LegacyNetworkAdapter = osTemplate.LegacyNetworkAdapter;
                            vm.RemoteDesktopEnabled = osTemplate.RemoteDesktop;
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    res.AddError(VirtualizationErrorCodes.GET_OS_TEMPLATES_ERROR, ex);
                    return res;
                }

                // setup VM paths
                string templatesPath = settings["OsTemplatesPath"];
                string rootFolderPattern = settings["RootFolder"];
                if(rootFolderPattern.IndexOf("[") == -1)
                {
                    // no pattern has been specified
                    if(!rootFolderPattern.EndsWith("\\"))
                        rootFolderPattern += "\\";
                    rootFolderPattern += "[username]\\[vps_hostname]";
                }

                vm.RootFolderPath = EvaluateItemVariables(rootFolderPattern, vm);
                vm.OperatingSystemTemplatePath = Path.Combine(templatesPath, osTemplateFile + ".vhd");
                vm.VirtualHardDrivePath = Path.Combine(vm.RootFolderPath, hostname + ".vhd");

                // save meta-item
                try
                {
                    vm.Id = PackageController.AddPackageItem(vm);
                }
                catch (Exception ex)
                {
                    res.AddError(VirtualizationErrorCodes.CREATE_META_ITEM_ERROR, ex);
                    return res;
                }
                
                #endregion

                #region Start Asynchronous task
                try
                {
                    // asynchronous process starts here
                    CreateServerAsyncWorker worker = new CreateServerAsyncWorker();

                    worker.TaskId = vm.CurrentTaskId; // async task ID
                    worker.ThreadUserId = SecurityContext.User.UserId;
                    worker.Item = vm;
                    worker.OsTemplate = osTemplate;

                    worker.ExternalAddressesNumber = externalAddressesNumber;
                    worker.RandomExternalAddresses = randomExternalAddresses;
                    worker.ExternalAddresses = externalAddresses;

                    worker.PrivateAddressesNumber = privateAddressesNumber;
                    worker.RandomPrivateAddresses = randomPrivateAddresses;
                    worker.PrivateAddresses = privateAddresses;

                    worker.SummaryLetterEmail = summaryLetterEmail;

                    worker.CreateAsync();
                }
                catch (Exception ex)
                {
                    // delete meta item
                    PackageController.DeletePackageItem(vm.Id);

                    // return from method
                    res.AddError(VirtualizationErrorCodes.CREATE_TASK_START_ERROR, ex);
                    return res;
                }
                #endregion
            }
            catch (Exception ex)
            {
                res.AddError(VirtualizationErrorCodes.CREATE_ERROR, ex);
                return res;
            }

            res.Value = vm.Id;
            res.IsSuccess = true;
            return res;
        }

        internal static void CreateVirtualMachineInternal(string taskId, VirtualMachine vm, LibraryItem osTemplate,
                int externalAddressesNumber, bool randomExternalAddresses, int[] externalAddresses,
                int privateAddressesNumber, bool randomPrivateAddresses, string[] privateAddresses,
                string summaryLetterEmail)
        {
            // start task
            TaskManager.StartTask(taskId, "VPS", "CREATE", vm.Name);
            TaskManager.ItemId = vm.Id;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // set Error flag
                vm.ProvisioningStatus = VirtualMachineProvisioningStatus.Error;

                // load proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // load service settings
                StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);

                #region Setup External network
                TaskManager.Write("VPS_CREATE_SETUP_EXTERNAL_NETWORK");

                try
                {
                    if (vm.ExternalNetworkEnabled)
                    {
                        // provision IP addresses
                        ResultObject privResult = AddVirtualMachineExternalIPAddresses(vm.Id, randomExternalAddresses,
                            externalAddressesNumber, externalAddresses, false);

                        // set primary IP address
                        NetworkAdapterDetails extNic = GetExternalNetworkAdapterDetails(vm.Id);
                        if (extNic.IPAddresses.Length > 0)
                            SetVirtualMachinePrimaryExternalIPAddress(vm.Id, extNic.IPAddresses[0].AddressId, false);

                        // connect to network
                        vm.ExternalSwitchId = settings["ExternalNetworkId"];
                        vm.ExternalNicMacAddress = GenerateMacAddress();
                    }
                    else
                    {
                        TaskManager.Write("VPS_CREATE_SETUP_EXTERNAL_NETWORK_SKIP");
                    }
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_SETUP_EXTERNAL_NETWORK_ERROR");
                    return;
                }
                #endregion

                #region Setup Management network
                TaskManager.Write("VPS_CREATE_SETUP_MANAGEMENT_NETWORK");

                try
                {
                    if (vm.ManagementNetworkEnabled)
                    {
                        // check that package contains unassigned IP
                        // that could be re-used
                        List<PackageIPAddress> packageIps = ServerController.GetPackageUnassignedIPAddresses(
                            vm.PackageId, IPAddressPool.VpsManagementNetwork);

                        if (packageIps.Count == 0)
                        {
                            // must be fresh space
                            // allocate package IP from the pool
                            List<IPAddressInfo> ips = ServerController.GetUnallottedIPAddresses(
                                vm.PackageId, ResourceGroups.VPS, IPAddressPool.VpsManagementNetwork);

                            if (ips.Count > 0)
                            {
                                // assign IP to the package
                                ServerController.AllocatePackageIPAddresses(vm.PackageId, new int[] { ips[0].AddressId });

                                // re-read package IPs
                                packageIps = ServerController.GetPackageUnassignedIPAddresses(
                                                vm.PackageId, IPAddressPool.VpsManagementNetwork);
                            }
                            else
                            {
                                // nothing to allocate - pool empty
                                TaskManager.WriteWarning("VPS_CREATE_SETUP_MANAGEMENT_NETWORK_POOL_EMPTY");
                            }
                        }

                        if (packageIps.Count > 0)
                        {
                            // assign to the item
                            ServerController.AddItemIPAddress(vm.Id, packageIps[0].PackageAddressID);

                            // set primary IP address
                            ServerController.SetItemPrimaryIPAddress(vm.Id, packageIps[0].PackageAddressID);

                            // connect to network
                            vm.ManagementSwitchId = settings["ManagementNetworkId"];
                            vm.ManagementNicMacAddress = GenerateMacAddress();
                        }
                    }
                    else
                    {
                        TaskManager.Write("VPS_CREATE_SETUP_MANAGEMENT_NETWORK_SKIP");
                    }
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_SETUP_MANAGEMENT_NETWORK_ERROR");
                    return;
                }
                #endregion

                #region Setup Private network
                TaskManager.Write("VPS_CREATE_SETUP_PRIVATE_NETWORK");

                try
                {
                    if (vm.PrivateNetworkEnabled)
                    {
                        NetworkAdapterDetails privNic = GetPrivateNetworkDetailsInternal(vm.ServiceId);

                        if (!privNic.IsDHCP)
                        {
                            // provision IP addresses
                            ResultObject extResult = AddVirtualMachinePrivateIPAddresses(vm.Id, randomPrivateAddresses, privateAddressesNumber, privateAddresses, false);

                            // set primary IP address
                            privNic = GetPrivateNetworkAdapterDetails(vm.Id);
                            if (privNic.IPAddresses.Length > 0)
                                SetVirtualMachinePrimaryPrivateIPAddress(vm.Id, privNic.IPAddresses[0].AddressId, false);
                        }

                        // connecto to network
                        vm.PrivateSwitchId = settings["PrivateNetworkId"];

                        if (String.IsNullOrEmpty(vm.PrivateSwitchId))
                        {
                            // create/load private virtual switch
                            vm.PrivateSwitchId = EnsurePrivateVirtualSwitch(vm);
                            if (vm.PrivateSwitchId == null)
                                return; // exit on error
                        }
                        vm.PrivateNicMacAddress = GenerateMacAddress();
                    }
                    else
                    {
                        TaskManager.Write("VPS_CREATE_SETUP_PRIVATE_NETWORK_SKIP");
                    }
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_SETUP_PRIVATE_NETWORK_ERROR");
                    return;
                }
                #endregion

                // update service item
                VirtualMachineProvisioningStatus status = vm.ProvisioningStatus;
                vm.ProvisioningStatus = VirtualMachineProvisioningStatus.InProgress;
                PackageController.UpdatePackageItem(vm);
                vm.ProvisioningStatus = status;

                #region Copy/convert VHD
                JobResult result = null;
                ReturnCode code = ReturnCode.OK;
                TaskManager.Write("VPS_CREATE_OS_TEMPLATE", osTemplate.Name);
                TaskManager.Write("VPS_CREATE_CONVERT_VHD");
                TaskManager.Write("VPS_CREATE_CONVERT_SOURCE_VHD", vm.OperatingSystemTemplatePath);
                TaskManager.Write("VPS_CREATE_CONVERT_DEST_VHD", vm.VirtualHardDrivePath);
                try
                {
                    // convert VHD
                    VirtualHardDiskType vhdType = (VirtualHardDiskType)Enum.Parse(typeof(VirtualHardDiskType), settings["VirtualDiskType"], true);
                    result = vs.ConvertVirtualHardDisk(vm.OperatingSystemTemplatePath, vm.VirtualHardDrivePath, vhdType);

                    // check return
                    if (result.ReturnValue != ReturnCode.JobStarted)
                    {
                        TaskManager.WriteError("VPS_CREATE_CONVERT_VHD_ERROR_JOB_START", result.ReturnValue.ToString());
                        return;
                    }

                    // wait for completion
                    if (!JobCompleted(vs, result.Job))
                    {
                        TaskManager.WriteError("VPS_CREATE_CONVERT_VHD_ERROR_JOB_EXEC", result.Job.ErrorDescription.ToString());
                        return;
                    }
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_CONVERT_VHD_ERROR");
                    return;
                }
                #endregion

                #region Get VHD info
                VirtualHardDiskInfo vhdInfo = null;
                try
                {
                    vhdInfo = vs.GetVirtualHardDiskInfo(vm.VirtualHardDrivePath);
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_GET_VHD_INFO");
                    return;
                }

                if (vhdInfo == null || vhdInfo.InUse)
                {
                    // master VHD is in use
                    TaskManager.WriteError("VPS_CREATE_MASTER_VHD_IN_USE");
                    return;
                }

                // check if it should be expanded
                int hddSizeGB = Convert.ToInt32(vhdInfo.MaxInternalSize / Size1G);

                TaskManager.Write("VPS_CREATE_EXPAND_SOURCE_VHD_SIZE", hddSizeGB.ToString());
                TaskManager.Write("VPS_CREATE_EXPAND_DEST_VHD_SIZE", vm.HddSize.ToString());
                #endregion

                #region Expand VHD
                bool expanded = false;
                if (vm.HddSize > hddSizeGB)
                {
                    TaskManager.Write("VPS_CREATE_EXPAND_VHD");

                    // expand VHD
                    try
                    {
                        result = vs.ExpandVirtualHardDisk(vm.VirtualHardDrivePath, (ulong)vm.HddSize);
                    }
                    catch (Exception ex)
                    {
                        TaskManager.WriteError(ex, "VPS_CREATE_EXPAND_VHD_ERROR");
                        return;
                    }

                    // check return
                    if (result.ReturnValue != ReturnCode.JobStarted)
                    {
                        // error starting Expand job
                        TaskManager.WriteError("VPS_CREATE_EXPAND_VHD_ERROR_JOB_START", result.ReturnValue.ToString());
                        return;
                    }

                    // wait for completion
                    if (!JobCompleted(vs, result.Job))
                    {
                        // error executing Expand job
                        TaskManager.WriteError("VPS_CREATE_EXPAND_VHD_ERROR_JOB_EXEC", result.Job.ErrorDescription);
                        return;
                    }
                    expanded = true;
                }
                else
                {
                    // skip expanding
                    TaskManager.Write("VPS_CREATE_EXPAND_VHD_SKIP");
                }
                #endregion

                #region Process VHD contents
                // mount VHD
                if ((expanded && osTemplate.ProcessVolume != -1)
                    || (osTemplate.SysprepFiles != null && osTemplate.SysprepFiles.Length > 0))
                {
                    try
                    {
                        #region Mount VHD
                        MountedDiskInfo mountedInfo = vs.MountVirtualHardDisk(vm.VirtualHardDrivePath);
                        if (mountedInfo == null)
                        {
                            // mount returned NULL
                            TaskManager.WriteError("VPS_CREATE_MOUNT_VHD_NULL");
                            return;
                        }
                        #endregion

                        #region Expand volume
                        if (expanded && osTemplate.ProcessVolume != -1 && mountedInfo.DiskVolumes.Length > 0)
                        {
                            try
                            {
                                vs.ExpandDiskVolume(mountedInfo.DiskAddress, mountedInfo.DiskVolumes[osTemplate.ProcessVolume]);
                            }
                            catch (Exception ex)
                            {
                                TaskManager.WriteError(ex, "VPS_CREATE_DISKPART_ERROR");
                            }
                        }
                        else
                        {
                            TaskManager.Write("VPS_CREATE_EXPAND_VHD_SKIP_NO_VOLUMES");
                        }
                        #endregion

                        #region Sysprep
                        if (mountedInfo.DiskVolumes.Length > 0
                            && osTemplate.ProcessVolume != -1
                            && osTemplate.SysprepFiles != null && osTemplate.SysprepFiles.Length > 0)
                        {
                            foreach (string remoteFile in osTemplate.SysprepFiles)
                            {
                                try
                                {
                                    TaskManager.Write("VPS_CREATE_SYSPREP_FILE", remoteFile);

                                    // build remote path
                                    string path = remoteFile;
                                    if (!remoteFile.StartsWith("\\"))
                                        path = remoteFile.Substring(remoteFile.IndexOf("\\"));

                                    path = String.Format("{0}:{1}", mountedInfo.DiskVolumes[osTemplate.ProcessVolume], path);

                                    // read remote file
                                    string contents = vs.ReadRemoteFile(path);
                                    if (contents == null)
                                    {
                                        TaskManager.Write("VPS_CREATE_SYSPREP_FILE_NOT_FOUND", remoteFile);
                                        continue;
                                    }

                                    // process file contents
                                    contents = EvaluateVirtualMachineTemplate(vm.Id, false, false, contents);

                                    // write remote file
                                    vs.WriteRemoteFile(path, contents);
                                }
                                catch (Exception ex)
                                {
                                    TaskManager.WriteError("VPS_CREATE_SYSPREP_FILE_ERROR", ex.Message);
                                }
                            }
                        }
                        #endregion

                        #region Unmount VHD
                        try
                        {
                            code = vs.UnmountVirtualHardDisk(vm.VirtualHardDrivePath);
                            if (code != ReturnCode.OK)
                            {
                                TaskManager.WriteError("VPS_CREATE_UNMOUNT_ERROR_JOB_START", code.ToString());
                                return;
                            }
                        }
                        catch (Exception ex)
                        {
                            TaskManager.WriteError(ex, "VPS_CREATE_UNMOUNT_ERROR");
                            return;
                        }
                        #endregion
                    }
                    catch (Exception ex)
                    {
                        // error mounting
                        TaskManager.WriteError(ex, "VPS_CREATE_MOUNT_VHD");
                        return;
                    }
                } // end if (expanded ...
                #endregion

                #region Create Virtual Machine
                TaskManager.Write("VPS_CREATE_CPU_CORES", vm.CpuCores.ToString());
                TaskManager.Write("VPS_CREATE_RAM_SIZE", vm.RamSize.ToString());
                TaskManager.Write("VPS_CREATE_CREATE_VM");
                // create virtual machine
                try
                {
                    // create
                    vm = vs.CreateVirtualMachine(vm);
                }
                catch (Exception ex)
                {
                    TaskManager.WriteError(ex, "VPS_CREATE_CREATE_VM_ERROR");
                    return;
                }

                // update meta item
                PackageController.UpdatePackageItem(vm);

                TaskManager.Write("VPS_CREATE_CREATED_VM");
                #endregion

                // set OK flag
                vm.ProvisioningStatus = VirtualMachineProvisioningStatus.OK;

                #region Send KVP
                // configure computer name
                if (osTemplate.ProvisionComputerName)
                {
                    TaskManager.Write("VPS_CREATE_SET_COMPUTER_NAME_KVP");
                    SendComputerNameKVP(vm.Id, vm.Name);
                }

                // change administrator password
                if (osTemplate.ProvisionAdministratorPassword)
                {
                    TaskManager.Write("VPS_CREATE_SET_PASSWORD_KVP");
                    SendAdministratorPasswordKVP(vm.Id, CryptoUtils.Decrypt(vm.AdministratorPassword));
                }

                // configure network adapters
                if(osTemplate.ProvisionNetworkAdapters)
                {
                    // external NIC
                    TaskManager.Write("VPS_CREATE_SET_EXTERNAL_NIC_KVP");
                    if (vm.ExternalNetworkEnabled)
                    {
                        result = SendNetworkAdapterKVP(vm.Id, "External");

                        if (result.ReturnValue != ReturnCode.JobStarted)
                            TaskManager.WriteWarning("VPS_CREATE_SET_EXTERNAL_NIC_KVP_ERROR", result.ReturnValue.ToString());
                    }

                    // management NIC
                    TaskManager.Write("VPS_CREATE_SET_MANAGEMENT_NIC_KVP");
                    if (vm.ManagementNetworkEnabled)
                    {
                        result = SendNetworkAdapterKVP(vm.Id, "Management");

                        if (result.ReturnValue != ReturnCode.JobStarted)
                            TaskManager.WriteWarning("VPS_CREATE_SET_MANAGEMENT_NIC_KVP_ERROR", result.ReturnValue.ToString());
                    }

                    // private NIC
                    TaskManager.Write("VPS_CREATE_SET_PRIVATE_NIC_KVP");
                    if (vm.PrivateNetworkEnabled)
                    {
                        result = SendNetworkAdapterKVP(vm.Id, "Private");

                        if (result.ReturnValue != ReturnCode.JobStarted)
                            TaskManager.WriteWarning("VPS_CREATE_SET_PRIVATE_NIC_KVP_ERROR", result.ReturnValue.ToString());
                    }
                }
                #endregion

                #region Start VPS
                TaskManager.Write("VPS_CREATE_START_VPS");

                try
                {
                    // start virtual machine
                    result = vs.ChangeVirtualMachineState(vm.VirtualMachineId, VirtualMachineRequestedState.Start);

                    // check return
                    if (result.ReturnValue == ReturnCode.JobStarted)
                    {
                        // wait for completion
                        if (!JobCompleted(vs, result.Job))
                        {
                            TaskManager.WriteWarning("VPS_CREATE_START_VPS_ERROR_JOB_EXEC", result.Job.ErrorDescription.ToString());
                        }
                    }
                    else
                    {
                        TaskManager.WriteWarning("VPS_CREATE_START_VPS_ERROR_JOB_START", result.ReturnValue.ToString());
                    }
                }
                catch (Exception ex)
                {
                    TaskManager.WriteWarning("VPS_CREATE_START_VPS_ERROR", ex.Message);
                }
                TaskManager.Write("VPS_CREATE_STARTED_VPS");
                #endregion

                #region Send Summary letter
                // send summary e-mail
                if (!String.IsNullOrEmpty(summaryLetterEmail))
                {
                    SendVirtualMachineSummaryLetter(vm.Id, summaryLetterEmail, null, true);
                }
                #endregion

            }
            catch (Exception ex)
            {
                TaskManager.WriteError(ex, VirtualizationErrorCodes.CREATE_ERROR);
                return;
            }
            finally
            {
                // reset task ID
                vm.CurrentTaskId = null;
                PackageController.UpdatePackageItem(vm);

                if (vm.ProvisioningStatus == VirtualMachineProvisioningStatus.OK)
                    TaskManager.Write("VPS_CREATE_SUCCESS");
                else if (vm.ProvisioningStatus == VirtualMachineProvisioningStatus.Error)
                    TaskManager.Write("VPS_CREATE_ERROR_END");

                // complete task
                TaskManager.CompleteTask();
            }
        }

        private static void CheckNumericQuota(PackageContext cntx, List<string> errors, string quotaName, int currentVal, int val, string messageKey)
        {
            CheckQuotaValue(cntx, errors, quotaName, currentVal, val, messageKey);
        }

        private static void CheckNumericQuota(PackageContext cntx, List<string> errors, string quotaName, int val, string messageKey)
        {
            CheckQuotaValue(cntx, errors, quotaName, 0, val, messageKey);
        }

        private static void CheckBooleanQuota(PackageContext cntx, List<string> errors, string quotaName, bool val, string messageKey)
        {
            CheckQuotaValue(cntx, errors, quotaName, 0, val ? 1 : 0, messageKey);
        }

        private static void CheckListsQuota(PackageContext cntx, List<string> errors, string quotaName, string messageKey)
        {
            CheckQuotaValue(cntx, errors, quotaName, 0, -1, messageKey);
        }

        private static void CheckQuotaValue(PackageContext cntx, List<string> errors, string quotaName, int currentVal, int val, string messageKey)
        {
            if (!cntx.Quotas.ContainsKey(quotaName))
                return;

            QuotaValueInfo quota = cntx.Quotas[quotaName];

            if(val == -1 && quota.QuotaExhausted) // check if quota already reached
            {
                errors.Add(messageKey + ":" + quota.QuotaAllocatedValue);
            }
            else if(quota.QuotaAllocatedValue == -1)
                return; // unlimited
            else if (quota.QuotaTypeId == 1 && val == 1 && quota.QuotaAllocatedValue == 0) // bool quota
                errors.Add(messageKey);
            else if (quota.QuotaTypeId == 2)
            {
                int maxValue = quota.QuotaAllocatedValue - quota.QuotaUsedValue + currentVal;
                if(val > maxValue)
                    errors.Add(messageKey + ":" + maxValue);
            }
            else if (quota.QuotaTypeId == 3 && val > quota.QuotaAllocatedValue)
            {
                int maxValue = quota.QuotaAllocatedValue;
                errors.Add(messageKey + ":" + maxValue);
            }
        }

        public static IntResult ImportVirtualMachine(int packageId,
            int serviceId, string vmId,
            string osTemplateFile, string adminPassword,
            bool startShutdownAllowed, bool pauseResumeAllowed, bool rebootAllowed, bool resetAllowed, bool reinstallAllowed,
            string externalNicMacAddress, int[] externalAddresses,
            string managementNicMacAddress, int managementAddress)
        {
            // result object
            IntResult res = new IntResult();

            // meta item
            VirtualMachine item = null;

            try
            {
                #region Check account and space statuses
                // check account
                if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive | DemandAccount.IsAdmin))
                    return res;

                // check package
                if (!SecurityContext.CheckPackage(res, packageId, DemandPackage.IsActive))
                    return res;

                #endregion

                // load package context
                PackageContext cntx = PackageController.GetPackageContext(packageId);

                item = new VirtualMachine();
                item.ServiceId = serviceId;
                item.PackageId = packageId;
                item.VirtualMachineId = vmId;

                // load service settings
                StringDictionary settings = ServerController.GetServiceSettings(serviceId);

                // load virtual machine info from service
                VirtualizationServer vs = GetVirtualizationProxy(serviceId);
                VirtualMachine vm = vs.GetVirtualMachineEx(vmId);

                // set VM properties
                item.Name = vm.Name;
                item.ProvisioningStatus = VirtualMachineProvisioningStatus.OK;

                item.CpuCores = vm.CpuCores;
                item.RamSize = vm.RamSize;
                item.HddSize = vm.HddSize;
                item.VirtualHardDrivePath = vm.VirtualHardDrivePath;
                item.RootFolderPath = Path.GetDirectoryName(vm.VirtualHardDrivePath);
                item.SnapshotsNumber = cntx.Quotas[Quotas.VPS_SNAPSHOTS_NUMBER].QuotaAllocatedValue;
                item.DvdDriveInstalled = vm.DvdDriveInstalled;
                item.BootFromCD = vm.BootFromCD;
                item.NumLockEnabled = vm.NumLockEnabled;
                item.StartTurnOffAllowed = startShutdownAllowed;
                item.PauseResumeAllowed = pauseResumeAllowed;
                item.RebootAllowed = rebootAllowed;
                item.ResetAllowed = resetAllowed;
                item.ReinstallAllowed = reinstallAllowed;

                // remote desktop
                if(!String.IsNullOrEmpty(adminPassword))
                {
                    item.RemoteDesktopEnabled = true;
                    item.AdministratorPassword = CryptoUtils.Encrypt(adminPassword);
                }

                // set OS template
                string templatesPath = settings["OsTemplatesPath"];
                item.OperatingSystemTemplatePath = Path.Combine(templatesPath, osTemplateFile + ".vhd");
                try
                {
                    LibraryItem[] osTemplates = GetOperatingSystemTemplatesByServiceId(serviceId);
                    foreach (LibraryItem osTemplate in osTemplates)
                    {
                        if (String.Compare(osTemplate.Path, osTemplateFile, true) == 0)
                        {
                            item.OperatingSystemTemplate = osTemplate.Name;
                            item.LegacyNetworkAdapter = osTemplate.LegacyNetworkAdapter;
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    res.AddError(VirtualizationErrorCodes.GET_OS_TEMPLATES_ERROR, ex);
                    return res;
                }

                // save item
                int itemId = PackageController.AddPackageItem(item);
                item.Id = itemId;
                res.Value = itemId;

                #region Setup external network
                // setup external network
                if (!String.IsNullOrEmpty(externalNicMacAddress))
                {
                    item.ExternalNetworkEnabled = true;
                    item.ExternalNicMacAddress = externalNicMacAddress;
                    item.ExternalSwitchId = settings["ExternalNetworkId"];

                    // assign selected IP addresses to package
                    ServerController.AllocatePackageIPAddresses(packageId, externalAddresses);

                    // re-read package IPs
                    List<PackageIPAddress> packageIPs = ServerController.GetPackageUnassignedIPAddresses(
                                    packageId, IPAddressPool.VpsExternalNetwork);

                    // assign IP addresses to VM
                    for(int i = 0; i < externalAddresses.Length; i++)
                    {
                        foreach (PackageIPAddress ip in packageIPs)
                        {
                            if (ip.AddressID == externalAddresses[i])
                            {
                                // assign to the item
                                ServerController.AddItemIPAddress(itemId, ip.PackageAddressID);

                                // set primary IP address
                                if(i == 0)
                                    ServerController.SetItemPrimaryIPAddress(itemId, ip.PackageAddressID);

                                break;
                            }
                        }
                    }
                }
                #endregion

                #region Setup management network
                // setup management network
                if (!String.IsNullOrEmpty(managementNicMacAddress))
                {
                    item.ManagementNetworkEnabled = true;
                    item.ManagementNicMacAddress = managementNicMacAddress;
                    item.ManagementSwitchId = settings["ManagementNetworkId"];

                    // assign selected IP addresses to package
                    ServerController.AllocatePackageIPAddresses(packageId, new int[] { managementAddress });

                    // re-read package IPs
                    List<PackageIPAddress> packageIPs = ServerController.GetPackageUnassignedIPAddresses(
                                    packageId, IPAddressPool.VpsManagementNetwork);

                    // assign IP addresses to VM
                    foreach (PackageIPAddress ip in packageIPs)
                    {
                        if (ip.AddressID == managementAddress)
                        {
                            // assign to the item
                            ServerController.AddItemIPAddress(itemId, ip.PackageAddressID);

                            break;
                        }
                    }
                }
                #endregion

                // save item once again
                PackageController.UpdatePackageItem(item);
            }
            catch (Exception ex)
            {
                res.AddError(VirtualizationErrorCodes.IMPORT_ERROR, ex);
                return res;
            }

            res.IsSuccess = true;
            return res;
        }

        private static JobResult SendNetworkAdapterKVP(int itemId, string adapterName)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);
            if (vm == null)
                return null;

            // build task parameters
            Dictionary<string, string> props = new Dictionary<string, string>();
            NetworkAdapterDetails nic = null;

            if(String.Compare(adapterName, "external", true) == 0)
            {
                // external
                nic = GetExternalNetworkAdapterDetails(itemId);
            }
            else if(String.Compare(adapterName, "private", true) == 0)
            {
                // private
                nic = GetPrivateNetworkAdapterDetails(itemId);
            }
            else
            {
                // management
                nic = GetManagementNetworkAdapterDetails(itemId);
            }
            
            // network format
            if (nic != null && !String.IsNullOrEmpty(nic.MacAddress))
            {
                props["MAC"] = nic.MacAddress.Replace("-", ":");
                props["EnableDHCP"] = nic.IsDHCP.ToString();
                if (!nic.IsDHCP)
                {
                    string[] ips = new string[nic.IPAddresses.Length];
                    string[] subnetMasks = new string[nic.IPAddresses.Length];
                    for (int i = 0; i < ips.Length; i++)
                    {
                        ips[i] = nic.IPAddresses[i].IPAddress;
                        subnetMasks[i] = nic.IPAddresses[i].SubnetMask;

                        // set gateway from the first (primary) IP
                        if (i == 0)
                            props["DefaultIPGateway"] = nic.IPAddresses[i].DefaultGateway;
                    }

                    props["IPAddress"] = String.Join(";", ips);
                    props["SubnetMask"] = String.Join(";", subnetMasks);

                    // name servers
                    props["PreferredDNSServer"] = nic.PreferredNameServer;
                    if (!String.IsNullOrEmpty(nic.AlternateNameServer))
                        props["PreferredDNSServer"] += ";" + nic.AlternateNameServer;
                }
            }

            // DNS
            if (!props.ContainsKey("PreferredDNSServer")
                || String.IsNullOrEmpty(props["PreferredDNSServer"]))
            {
                props["PreferredDNSServer"] = "0.0.0.0"; // obtain automatically
            }

            // send items
            return SendKvpItems(itemId, "SetupNetworkAdapter", props);
        }

        private static string GetSymbolDelimitedMacAddress(string mac, string delimiter)
        {
            if (String.IsNullOrEmpty(mac))
                return mac;

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 6; i++)
            {
                sb.Append(mac[i * 2]).Append(mac[i * 2 + 1]);
                if (i < 5) sb.Append(delimiter);
            }
            return sb.ToString();
        }

        private static JobResult SendComputerNameKVP(int itemId, string computerName)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);
            if (vm == null)
                return null;

            // build task parameters
            Dictionary<string, string> props = new Dictionary<string, string>();

            props["FullComputerName"] = computerName;

            // send items
            return SendKvpItems(itemId, "ChangeComputerName", props);
        }

        private static JobResult SendAdministratorPasswordKVP(int itemId, string password)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);
            if (vm == null)
                return null;

            // build task parameters
            Dictionary<string, string> props = new Dictionary<string, string>();

            props["Password"] = password;

            // send items
            return SendKvpItems(itemId, "ChangeAdministratorPassword", props);
        }

        private static JobResult SendKvpItems(int itemId, string taskName, Dictionary<string, string> taskProps)
        {
            string TASK_PREFIX = "WSP-";

            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);
            if (vm == null)
                return null;

            JobResult result = null;

            // load proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

            try
            {
                // delete completed task definitions
                List<string> completedTasks = new List<string>();
                KvpExchangeDataItem[] vmKvps = vs.GetKVPItems(vm.VirtualMachineId);
                foreach (KvpExchangeDataItem vmKvp in vmKvps)
                {
                    if (vmKvp.Name.StartsWith(TASK_PREFIX))
                        completedTasks.Add(vmKvp.Name);
                }

                // delete completed items
                vs.RemoveKVPItems(vm.VirtualMachineId, completedTasks.ToArray());
            }
            catch (Exception ex)
            {
                // log error
                TaskManager.WriteWarning(String.Format("Error deleting KVP items: {0}", ex.Message));
            }

            // build items array
            List<string> items = new List<string>();
            foreach (string propName in taskProps.Keys)
                items.Add(propName + "=" + taskProps[propName]);

            taskName = String.Format("{0}{1}-{2}", TASK_PREFIX, taskName, DateTime.Now.Ticks);
            string taskData = String.Join("|", items.ToArray());

            // create KVP item
            KvpExchangeDataItem[] kvp = new KvpExchangeDataItem[1];
            kvp[0] = new KvpExchangeDataItem();
            kvp[0].Name = taskName;
            kvp[0].Data = taskData;

            try
            {
                // try adding KVP items
                result = vs.AddKVPItems(vm.VirtualMachineId, kvp);

                if (result.Job != null && result.Job.JobState == ConcreteJobState.Exception)
                {
                    // try updating KVP items
                    return vs.ModifyKVPItems(vm.VirtualMachineId, kvp);
                }
                else
                {
                    return result;
                }
            }
            catch (Exception ex)
            {
                // log error
                TaskManager.WriteWarning(String.Format("Error setting KVP items '{0}': {1}", kvp[0].Data, ex.Message));
            }

            return null;
        }

        private static string EnsurePrivateVirtualSwitch(ServiceProviderItem item)
        {
            // try locate switch in the package
            List<ServiceProviderItem> items = PackageController.GetPackageItemsByType(item.PackageId, typeof(VirtualSwitch));

            // exists - return ID
            if (items.Count > 0)
                return ((VirtualSwitch)items[0]).SwitchId;

            // switch name
            string name = EvaluateItemVariables("[username] - [space_name]", item);

            // log
            TaskManager.Write("VPS_CREATE_PRIVATE_VIRTUAL_SWITCH", name);

            try
            {
                // create switch
                // load proxy
                VirtualizationServer vs = GetVirtualizationProxy(item.ServiceId);

                // create switch
                VirtualSwitch sw = vs.CreateSwitch(name);
                sw.ServiceId = item.ServiceId;
                sw.PackageId = item.PackageId;

                // save item
                PackageController.AddPackageItem(sw);

                return sw.SwitchId;
            }
            catch (Exception ex)
            {
                TaskManager.WriteError(ex, "VPS_CREATE_PRIVATE_VIRTUAL_SWITCH_ERROR");
                return null;
            }
        }

        private static string EvaluateItemVariables(string str, ServiceProviderItem item)
        {
            str = Utils.ReplaceStringVariable(str, "vps_hostname", item.Name);

            return EvaluateSpaceVariables(str, item.PackageId);
        }

        private static string EvaluateSpaceVariables(string str, int packageId)
        {
            // load package
            PackageInfo package = PackageController.GetPackage(packageId);
            UserInfo user = UserController.GetUser(package.UserId);
            str = Utils.ReplaceStringVariable(str, "space_id", packageId.ToString());
            str = Utils.ReplaceStringVariable(str, "space_name", package.PackageName);
            str = Utils.ReplaceStringVariable(str, "user_id", user.UserId.ToString());
            str = Utils.ReplaceStringVariable(str, "username", user.Username);

            return str;
        }
        #endregion

        #region VPS – General

        public static List<ConcreteJob> GetVirtualMachineJobs(int itemId)
        {
            // load meta item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            if (vm == null)
                return null;

            // get proxy
            VirtualizationServer vps = GetVirtualizationProxy(vm.ServiceId);

            // load jobs
            ConcreteJob[] jobs = vps.GetVirtualMachineJobs(vm.VirtualMachineId);
            List<ConcreteJob> retJobs = new List<ConcreteJob>();

            foreach (ConcreteJob job in jobs)
            {
                if (job.JobState == ConcreteJobState.Running)
                {
                    retJobs.Add(job);
                }
            }

            return retJobs;
        }
        
        public static byte[] GetVirtualMachineThumbnail(int itemId, ThumbnailSize size)
        {
            // load meta item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            if (vm == null)
                return null;

            // get proxy
            VirtualizationServer vps = GetVirtualizationProxy(vm.ServiceId);

            // return thumbnail
            return vps.GetVirtualMachineThumbnailImage(vm.VirtualMachineId, size);
        }

        public static VirtualMachine GetVirtualMachineGeneralDetails(int itemId)
        {
            // load meta item
            VirtualMachine machine = GetVirtualMachineByItemId(itemId);

            if (machine == null || String.IsNullOrEmpty(machine.VirtualMachineId))
                return null;

            // get proxy
            VirtualizationServer vps = GetVirtualizationProxy(machine.ServiceId);

            // load details
            VirtualMachine vm = vps.GetVirtualMachine(machine.VirtualMachineId);

            // add meta props
            vm.Id = machine.Id;
            vm.Name = machine.Name;
            vm.RamSize = machine.RamSize;
            vm.ServiceId = machine.ServiceId;

            return vm;
        }

        public static VirtualMachine GetVirtualMachineExtendedInfo(int serviceId, string vmId)
        {
            // get proxy
            VirtualizationServer vps = GetVirtualizationProxy(serviceId);

            // load details
            return vps.GetVirtualMachineEx(vmId);
        }

        public static int CancelVirtualMachineJob(string jobId)
        {
            // VPS - CANCEL_JOB
            return 0;
        }

        public static ResultObject UpdateVirtualMachineHostName(int itemId, string hostname, bool updateNetBIOS)
        {
            if (String.IsNullOrEmpty(hostname))
                throw new ArgumentNullException("hostname");

            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "UPDATE_HOSTNAME");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // update virtual machine name
                JobResult result = vs.RenameVirtualMachine(vm.VirtualMachineId, hostname);
                if (result.ReturnValue != ReturnCode.OK)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }

                // update meta item
                vm.Name = hostname;
                PackageController.UpdatePackageItem(vm);
                
                // update NetBIOS name if required
                if (updateNetBIOS)
                {
                    result = SendComputerNameKVP(itemId, hostname);
                    if (result.ReturnValue != ReturnCode.JobStarted
                        && result.Job.JobState == ConcreteJobState.Completed)
                    {
                        LogReturnValueResult(res, result);
                        TaskManager.CompleteResultTask(res);
                        return res;
                    }
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.CHANGE_ADMIN_PASSWORD_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject ChangeVirtualMachineStateExternal(int itemId, VirtualMachineRequestedState state)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            #region Check Quotas
            // check quotas
            List<string> quotaResults = new List<string>();

            if ((state == VirtualMachineRequestedState.Start
                || state == VirtualMachineRequestedState.TurnOff
                || state == VirtualMachineRequestedState.ShutDown)
                && !vm.StartTurnOffAllowed)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_START_SHUTDOWN_ALLOWED);

            else if ((state == VirtualMachineRequestedState.Pause
                || state == VirtualMachineRequestedState.Resume)
                && !vm.PauseResumeAllowed)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_PAUSE_RESUME_ALLOWED);

            else if (state == VirtualMachineRequestedState.Reboot
                && !vm.RebootAllowed)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_REBOOT_ALLOWED);

            else if (state == VirtualMachineRequestedState.Reset
                && !vm.ResetAllowed)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_EXCEEDED_RESET_ALOWED);

            if (quotaResults.Count > 0)
            {
                res.ErrorCodes.AddRange(quotaResults);
                res.IsSuccess = false;
                TaskManager.CompleteResultTask();
                return res;
            }
            #endregion

            return ChangeVirtualMachineState(itemId, state);
        }

        private static ResultObject ChangeVirtualMachineState(int itemId, VirtualMachineRequestedState state)
        {
            // start task
            ResultObject res = TaskManager.StartResultTask<ResultObject>("VPS", "CHANGE_STATE");
                                                
            try
            {
                // load service item
                VirtualMachine machine = (VirtualMachine)PackageController.GetPackageItem(itemId);
                if (machine == null)
                {
                    TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                    return res;
                }

                TaskManager.ItemId = machine.Id;
                TaskManager.ItemName = machine.Name;
                TaskManager.PackageId = machine.PackageId;
                TaskManager.WriteParameter("New state", state);

                // load proxy
                VirtualizationServer vps = GetVirtualizationProxy(machine.ServiceId);                    

                try
                {
                    if (state == VirtualMachineRequestedState.ShutDown)
                    {
                        ReturnCode code = vps.ShutDownVirtualMachine(machine.VirtualMachineId, true, SHUTDOWN_REASON);
                        if (code != ReturnCode.OK)
                        {
                            res.ErrorCodes.Add(VirtualizationErrorCodes.JOB_START_ERROR + ":" + code);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }

                        // spin until fully stopped
                        VirtualMachine vm = vps.GetVirtualMachine(machine.VirtualMachineId);
                        while (vm.State != VirtualMachineState.Off)
                        {
                            System.Threading.Thread.Sleep(1000); // sleep 1 second
                            vm = vps.GetVirtualMachine(machine.VirtualMachineId);
                        }
                    }
                    else if (state == VirtualMachineRequestedState.Reboot)
                    {
                        // shutdown first
                        ResultObject shutdownResult = ChangeVirtualMachineState(itemId, VirtualMachineRequestedState.ShutDown);
                        if(!shutdownResult.IsSuccess)
                        {
                            TaskManager.CompleteResultTask(res);
                            return shutdownResult;
                        }

                        // start machine
                        ResultObject startResult = ChangeVirtualMachineState(itemId, VirtualMachineRequestedState.Start);
                        if (!startResult.IsSuccess)
                        {
                            TaskManager.CompleteResultTask(res);
                            return startResult;
                        }
                    }
                    else
                    {
                        if (state == VirtualMachineRequestedState.Resume)
                            state = VirtualMachineRequestedState.Start;

                        JobResult result = vps.ChangeVirtualMachineState(machine.VirtualMachineId, state);

                        // check return
                        if (result.ReturnValue != ReturnCode.JobStarted)
                        {
                            LogReturnValueResult(res, result);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }

                        // wait for completion
                        if (!JobCompleted(vps, result.Job))
                        {
                            LogJobResult(res, result.Job);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }
                    }
                }
                catch(Exception ex)
                {
                    res.IsSuccess = false;
                    res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_CHANGE_VIRTUAL_SERVER_STATE);
                    TaskManager.WriteError(ex);
                }
            }
            catch (Exception ex)
            {
                res.IsSuccess = false;
                res.ErrorCodes.Add(VirtualizationErrorCodes.CHANGE_VIRTUAL_MACHINE_STATE_GENERAL_ERROR);
                TaskManager.WriteError(ex);
                return res;
            }

            TaskManager.CompleteTask();
            return res;
        }
        #endregion

        #region VPS - Configuration
        public static ResultObject ChangeAdministratorPassword(int itemId, string password)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "CHANGE_ADMIN_PASSWORD");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // change administrator password
                JobResult result = SendAdministratorPasswordKVP(itemId, password);
                if (result.ReturnValue != ReturnCode.JobStarted
                    && result.Job.JobState == ConcreteJobState.Completed)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }

                // update meta item
                vm.AdministratorPassword = CryptoUtils.Encrypt(password);
                PackageController.UpdatePackageItem(vm);
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.CHANGE_ADMIN_PASSWORD_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }
        #endregion

        #region VPS – Edit Configuration
        public static ResultObject UpdateVirtualMachineConfiguration(int itemId, int cpuCores, int ramMB, int hddGB, int snapshots,
                    bool dvdInstalled, bool bootFromCD, bool numLock,
                    bool startShutdownAllowed, bool pauseResumeAllowed, bool rebootAllowed, bool resetAllowed, bool reinstallAllowed,
                    bool externalNetworkEnabled,
                    bool privateNetworkEnabled)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            #region Check Quotas
            // check quotas
            List<string> quotaResults = new List<string>();
            PackageContext cntx = PackageController.GetPackageContext(vm.PackageId);

            CheckNumericQuota(cntx, quotaResults, Quotas.VPS_CPU_NUMBER, cpuCores, VirtualizationErrorCodes.QUOTA_EXCEEDED_CPU);
            CheckNumericQuota(cntx, quotaResults, Quotas.VPS_RAM, vm.RamSize, ramMB, VirtualizationErrorCodes.QUOTA_EXCEEDED_RAM);
            CheckNumericQuota(cntx, quotaResults, Quotas.VPS_HDD, vm.HddSize, hddGB, VirtualizationErrorCodes.QUOTA_EXCEEDED_HDD);
            CheckNumericQuota(cntx, quotaResults, Quotas.VPS_SNAPSHOTS_NUMBER, snapshots, VirtualizationErrorCodes.QUOTA_EXCEEDED_SNAPSHOTS);

            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_DVD_ENABLED, dvdInstalled, VirtualizationErrorCodes.QUOTA_EXCEEDED_DVD_ENABLED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_BOOT_CD_ALLOWED, bootFromCD, VirtualizationErrorCodes.QUOTA_EXCEEDED_CD_ALLOWED);

            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_START_SHUTDOWN_ALLOWED, startShutdownAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_START_SHUTDOWN_ALLOWED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_PAUSE_RESUME_ALLOWED, pauseResumeAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_PAUSE_RESUME_ALLOWED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_REBOOT_ALLOWED, rebootAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_REBOOT_ALLOWED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_RESET_ALOWED, resetAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_RESET_ALOWED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_REINSTALL_ALLOWED, reinstallAllowed, VirtualizationErrorCodes.QUOTA_EXCEEDED_REINSTALL_ALLOWED);

            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_EXTERNAL_NETWORK_ENABLED, externalNetworkEnabled, VirtualizationErrorCodes.QUOTA_EXCEEDED_EXTERNAL_NETWORK_ENABLED);
            CheckBooleanQuota(cntx, quotaResults, Quotas.VPS_PRIVATE_NETWORK_ENABLED, privateNetworkEnabled, VirtualizationErrorCodes.QUOTA_EXCEEDED_PRIVATE_NETWORK_ENABLED);

            // check acceptable values
            if (ramMB <= 0)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_RAM);
            if (hddGB <= 0)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_HDD);
            if (snapshots < 0)
                quotaResults.Add(VirtualizationErrorCodes.QUOTA_WRONG_SNAPSHOTS);

            if (quotaResults.Count > 0)
            {
                res.ErrorCodes.AddRange(quotaResults);
                return res;
            }
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "UPDATE_CONFIGURATION");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                JobResult result = null;

                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // stop VPS if required
                VirtualMachine vps = vs.GetVirtualMachine(vm.VirtualMachineId);

                bool wasStarted = false;

                // stop (shut down) virtual machine
                if (vps.State != VirtualMachineState.Off)
                {
                    wasStarted = true;
                    ReturnCode code = vs.ShutDownVirtualMachine(vm.VirtualMachineId, true, SHUTDOWN_REASON_CHANGE_CONFIG);
                    if (code == ReturnCode.OK)
                    {
                        // spin until fully stopped
                        vps = vs.GetVirtualMachine(vm.VirtualMachineId);
                        while (vps.State != VirtualMachineState.Off)
                        {
                            System.Threading.Thread.Sleep(1000); // sleep 1 second
                            vps = vs.GetVirtualMachine(vm.VirtualMachineId);
                        }
                    }
                    else
                    {
                        // turn off
                        result = vs.ChangeVirtualMachineState(vm.VirtualMachineId, VirtualMachineRequestedState.TurnOff);
                        if (!JobCompleted(vs, result.Job))
                        {
                            LogJobResult(res, result.Job);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }
                    }
                } // end OFF

                // update meta-item
                vm.CpuCores = cpuCores;
                vm.RamSize = ramMB;
                vm.HddSize = hddGB;
                vm.SnapshotsNumber = snapshots;

                vm.BootFromCD = bootFromCD;
                vm.NumLockEnabled = numLock;
                vm.DvdDriveInstalled = dvdInstalled;

                vm.StartTurnOffAllowed = startShutdownAllowed;
                vm.PauseResumeAllowed = pauseResumeAllowed;
                vm.ResetAllowed = resetAllowed;
                vm.RebootAllowed = rebootAllowed;
                vm.ReinstallAllowed = reinstallAllowed;

                vm.ExternalNetworkEnabled = externalNetworkEnabled;
                vm.PrivateNetworkEnabled = privateNetworkEnabled;

                // load service settings
                StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);

                #region setup external network
                if (vm.ExternalNetworkEnabled
                    && String.IsNullOrEmpty(vm.ExternalNicMacAddress))
                {
                    // connect to network
                    vm.ExternalSwitchId = settings["ExternalNetworkId"];
                    vm.ExternalNicMacAddress = GenerateMacAddress();
                }
                #endregion

                #region setup private network
                if (vm.PrivateNetworkEnabled
                    && String.IsNullOrEmpty(vm.PrivateNicMacAddress))
                {
                    // connecto to network
                    vm.PrivateSwitchId = settings["PrivateNetworkId"];

                    if (String.IsNullOrEmpty(vm.PrivateSwitchId))
                    {
                        // create/load private virtual switch
                        vm.PrivateSwitchId = EnsurePrivateVirtualSwitch(vm);
                    }
                    vm.PrivateNicMacAddress = GenerateMacAddress();
                }
                #endregion

                // update configuration on virtualization server
                vm = vs.UpdateVirtualMachine(vm);

                // update meta item
                PackageController.UpdatePackageItem(vm);

                // unprovision external IP addresses
                if (!vm.ExternalNetworkEnabled)
                    ServerController.DeleteItemIPAddresses(itemId);
                else
                    // send KVP config items
                    SendNetworkAdapterKVP(itemId, "External");

                // unprovision private IP addresses
                if (!vm.PrivateNetworkEnabled)
                    DataProvider.DeleteItemPrivateIPAddresses(SecurityContext.User.UserId, itemId);
                else
                    // send KVP config items
                    SendNetworkAdapterKVP(itemId, "Private");

                // start if required
                if (wasStarted)
                {
                    result = vs.ChangeVirtualMachineState(vm.VirtualMachineId, VirtualMachineRequestedState.Start);
                    if (!JobCompleted(vs, result.Job))
                    {
                        LogJobResult(res, result.Job);
                        TaskManager.CompleteResultTask(res);
                        return res;
                    }
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.CHANGE_VM_CONFIGURATION, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }
        #endregion

        #region DVD
        public static LibraryItem GetInsertedDvdDisk(int itemId)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);
            string isoPath = vs.GetInsertedDVD(vm.VirtualMachineId);

            if (String.IsNullOrEmpty(isoPath))
                return null;

            // load library items
            LibraryItem[] disks = GetLibraryDisks(itemId);

            // find required disk
            isoPath = Path.GetFileName(isoPath);
            foreach (LibraryItem disk in disks)
            {
                if (String.Compare(isoPath, disk.Path, true) == 0)
                    return disk;
            }
            return null;
        }

        public static LibraryItem[] GetLibraryDisks(int itemId)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);
            string path = settings["DvdLibraryPath"];

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

            return vs.GetLibraryItems(path);
        }

        public static ResultObject InsertDvdDisk(int itemId, string isoPath)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "INSERT_DVD_DISK");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // load service settings
                StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);
                string libPath = settings["DvdLibraryPath"];

                // combine full path
                string fullPath = Path.Combine(libPath, isoPath);

                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // insert DVD
                JobResult result = vs.InsertDVD(vm.VirtualMachineId, fullPath);
                if (result.ReturnValue != ReturnCode.OK)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.INSERT_DVD_DISK_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject EjectDvdDisk(int itemId)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "EJECT_DVD_DISK");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // insert DVD
                JobResult result = vs.EjectDVD(vm.VirtualMachineId);
                if (result.ReturnValue != ReturnCode.OK)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.EJECT_DVD_DISK_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }
        #endregion

        #region Snaphosts
        public static VirtualMachineSnapshot[] GetVirtualMachineSnapshots(int itemId)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);
            return vs.GetVirtualMachineSnapshots(vm.VirtualMachineId);
        }

        public static VirtualMachineSnapshot GetSnapshot(int itemId, string snaphostId)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);
            return vs.GetSnapshot(snaphostId);
        }

        public static ResultObject CreateSnapshot(int itemId)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "TAKE_SNAPSHOT");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                #region Check Quotas
                // check quotas
                List<string> quotaResults = new List<string>();
                PackageContext cntx = PackageController.GetPackageContext(vm.PackageId);

                                // check the number of created snapshots
                int createdNumber = vs.GetVirtualMachineSnapshots(vm.VirtualMachineId).Length;
                if (createdNumber >= vm.SnapshotsNumber)
                {
                    TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.QUOTA_EXCEEDED_SNAPSHOTS + ":" + vm.SnapshotsNumber);
                    return res;
                }
                #endregion

                // take snapshot
                JobResult result = vs.CreateSnapshot(vm.VirtualMachineId);
                if (result.ReturnValue != ReturnCode.JobStarted)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }

                if (!JobCompleted(vs, result.Job))
                {
                    LogJobResult(res, result.Job);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.TAKE_SNAPSHOT_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject ApplySnapshot(int itemId, string snapshotId)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "APPLY_SNAPSHOT");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                JobResult result = null;

                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // check VM state
                VirtualMachine vps = vs.GetVirtualMachine(vm.VirtualMachineId);

                // stop virtual machine
                if (vps.State != VirtualMachineState.Off)
                {
                    result = vs.ChangeVirtualMachineState(vm.VirtualMachineId, VirtualMachineRequestedState.TurnOff);
                    if (!JobCompleted(vs, result.Job))
                    {
                        LogJobResult(res, result.Job);
                        TaskManager.CompleteResultTask(res);
                        return res;
                    }
                }

                // take snapshot
                result = vs.ApplySnapshot(vm.VirtualMachineId, snapshotId);
                if (result.ReturnValue != ReturnCode.OK)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.APPLY_SNAPSHOT_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject RenameSnapshot(int itemId, string snapshotId, string newName)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "RENAME_SNAPSHOT");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // take snapshot
                JobResult result = vs.RenameSnapshot(vm.VirtualMachineId, snapshotId, newName);
                if (result.ReturnValue != ReturnCode.OK)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.RENAME_SNAPSHOT_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject DeleteSnapshot(int itemId, string snapshotId)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "DELETE_SNAPSHOT");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // take snapshot
                JobResult result = vs.DeleteSnapshot(snapshotId);
                if (result.ReturnValue != ReturnCode.JobStarted)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }

                if (!JobCompleted(vs, result.Job))
                {
                    LogJobResult(res, result.Job);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.DELETE_SNAPSHOT_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject DeleteSnapshotSubtree(int itemId, string snapshotId)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "DELETE_SNAPSHOT_SUBTREE");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // take snapshot
                JobResult result = vs.DeleteSnapshotSubtree(snapshotId);
                if (result.ReturnValue != ReturnCode.JobStarted)
                {
                    LogReturnValueResult(res, result);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }

                if (!JobCompleted(vs, result.Job))
                {
                    LogJobResult(res, result.Job);
                    TaskManager.CompleteResultTask(res);
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.DELETE_SNAPSHOT_SUBTREE_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static byte[] GetSnapshotThumbnail(int itemId, string snapshotId, ThumbnailSize size)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // get proxy
            VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

            return vs.GetSnapshotThumbnailImage(snapshotId, size);
        }
        #endregion

        #region Network - External
        public static NetworkAdapterDetails GetExternalNetworkDetails(int packageId)
        {
            // load service
            int serviceId = PackageController.GetPackageServiceId(packageId, ResourceGroups.VPS);

            return GetExternalNetworkDetailsInternal(serviceId);
        }

        public static NetworkAdapterDetails GetExternalNetworkAdapterDetails(int itemId)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // get default NIC
            NetworkAdapterDetails nic = GetExternalNetworkDetailsInternal(vm.ServiceId);

            // update NIC
            nic.MacAddress = GetSymbolDelimitedMacAddress(vm.ExternalNicMacAddress, "-");

            // load IP addresses
            nic.IPAddresses = ObjectUtils.CreateListFromDataReader<NetworkAdapterIPAddress>(
                DataProvider.GetItemIPAddresses(SecurityContext.User.UserId, itemId, (int)IPAddressPool.VpsExternalNetwork)).ToArray();

            // update subnet CIDR
            foreach (NetworkAdapterIPAddress ip in nic.IPAddresses)
                ip.SubnetMaskCidr = GetSubnetMaskCidr(ip.SubnetMask);

            if (nic.IPAddresses.Length > 0)
            {
                // from primary address
                nic.SubnetMask = nic.IPAddresses[0].SubnetMask;
                nic.SubnetMaskCidr = GetSubnetMaskCidr(nic.SubnetMask);
                nic.DefaultGateway = nic.IPAddresses[0].DefaultGateway;
            }

            return nic;
        }

        public static NetworkAdapterDetails GetManagementNetworkDetails(int packageId)
        {
            // load service
            int serviceId = PackageController.GetPackageServiceId(packageId, ResourceGroups.VPS);
            return GetManagementNetworkDetailsInternal(serviceId);
        }

        private static NetworkAdapterDetails GetExternalNetworkDetailsInternal(int serviceId)
        {
            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(serviceId);

            // create NIC object
            NetworkAdapterDetails nic = new NetworkAdapterDetails();
            nic.NetworkId = settings["ExternalNetworkId"];
            nic.PreferredNameServer = settings["ExternalPreferredNameServer"];
            nic.AlternateNameServer = settings["ExternalAlternateNameServer"];
            return nic;
        }

        public static NetworkAdapterDetails GetManagementNetworkAdapterDetails(int itemId)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // get default NIC
            NetworkAdapterDetails nic = GetManagementNetworkDetailsInternal(vm.ServiceId);

            // update NIC
            nic.MacAddress = GetSymbolDelimitedMacAddress(vm.ManagementNicMacAddress, "-");

            // load IP addresses
            nic.IPAddresses = ObjectUtils.CreateListFromDataReader<NetworkAdapterIPAddress>(
                DataProvider.GetItemIPAddresses(SecurityContext.User.UserId, itemId, (int)IPAddressPool.VpsManagementNetwork)).ToArray();

            // update subnet CIDR
            foreach (NetworkAdapterIPAddress ip in nic.IPAddresses)
                ip.SubnetMaskCidr = GetSubnetMaskCidr(ip.SubnetMask);

            if (nic.IPAddresses.Length > 0)
            {
                // from primary address
                nic.SubnetMask = nic.IPAddresses[0].SubnetMask;
                nic.SubnetMaskCidr = GetSubnetMaskCidr(nic.SubnetMask);
                nic.DefaultGateway = nic.IPAddresses[0].DefaultGateway;
            }

            return nic;
        }

        private static NetworkAdapterDetails GetManagementNetworkDetailsInternal(int serviceId)
        {
            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(serviceId);

            // create NIC object
            NetworkAdapterDetails nic = new NetworkAdapterDetails();
            nic.NetworkId = settings["ManagementNetworkId"];
            nic.IsDHCP = (String.Compare(settings["ManagementNicConfig"], "DHCP", true) == 0);

            if (!nic.IsDHCP)
            {
                nic.PreferredNameServer = settings["ManagementPreferredNameServer"];
                nic.AlternateNameServer = settings["ManagementAlternateNameServer"];
            }
            return nic;
        }

        public static ResultObject AddVirtualMachineExternalIPAddresses(int itemId, bool selectRandom, int addressesNumber, int[] addressIds, bool provisionKvp)
        {
            if (addressIds == null)
                throw new ArgumentNullException("addressIds");

            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "ADD_EXTERNAL_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                if (selectRandom)
                {
                    List<PackageIPAddress> ips = ServerController.GetPackageUnassignedIPAddresses(vm.PackageId, IPAddressPool.VpsExternalNetwork);
                    if (addressesNumber > ips.Count)
                    {
                        TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.NOT_ENOUGH_PACKAGE_IP_ADDRESSES);
                        return res;
                    }

                    // get next N unassigned addresses
                    addressIds = new int[addressesNumber];
                    for (int i = 0; i < addressesNumber; i++)
                        addressIds[i] = ips[i].PackageAddressID;
                }

                // add addresses
                foreach (int addressId in addressIds)
                    ServerController.AddItemIPAddress(itemId, addressId);

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "External");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.ADD_VIRTUAL_MACHINE_EXTERNAL_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject SetVirtualMachinePrimaryExternalIPAddress(int itemId, int packageAddressId, bool provisionKvp)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "SET_PRIMARY_EXTERNAL_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // call database
                ServerController.SetItemPrimaryIPAddress(itemId, packageAddressId);

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "External");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.SET_VIRTUAL_MACHINE_PRIMARY_EXTERNAL_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject DeleteVirtualMachineExternalIPAddresses(int itemId, int[] packageAddressIds, bool provisionKvp)
        {
            if (packageAddressIds == null)
                throw new ArgumentNullException("addressIds");

            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "DELETE_EXTERNAL_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // call database
                foreach (int packageAddressId in packageAddressIds)
                    ServerController.DeleteItemIPAddress(itemId, packageAddressId);

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "External");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.DELETE_VIRTUAL_MACHINE_EXTERNAL_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }
        #endregion

        #region Network – Private
        public static NetworkAdapterDetails GetPrivateNetworkDetails(int packageId)
        {
            // load service
            int serviceId = PackageController.GetPackageServiceId(packageId, ResourceGroups.VPS);

            return GetPrivateNetworkDetailsInternal(serviceId);
        }

        public static NetworkAdapterDetails GetPrivateNetworkAdapterDetails(int itemId)
        {
            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // load default internal adapter
            NetworkAdapterDetails nic = GetPrivateNetworkDetailsInternal(vm.ServiceId);

            // update NIC
            nic.MacAddress = GetSymbolDelimitedMacAddress(vm.PrivateNicMacAddress, "-");

            // load IP addresses
            nic.IPAddresses = ObjectUtils.CreateListFromDataReader<NetworkAdapterIPAddress>(
                DataProvider.GetItemPrivateIPAddresses(SecurityContext.User.UserId, itemId)).ToArray();

            foreach (NetworkAdapterIPAddress ip in nic.IPAddresses)
            {
                ip.SubnetMask = nic.SubnetMask;
                ip.SubnetMaskCidr = nic.SubnetMaskCidr;
                ip.DefaultGateway = nic.DefaultGateway;
            }

            return nic;
        }

        private static NetworkAdapterDetails GetPrivateNetworkDetailsInternal(int serviceId)
        {
            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(serviceId);

            // create NIC object
            NetworkAdapterDetails nic = new NetworkAdapterDetails();

            string networkFormat = settings["PrivateNetworkFormat"];
            if (String.IsNullOrEmpty(networkFormat))
            {
                // custom format
				nic.NetworkFormat = settings["PrivateIPAddress"];
				var v6 = IPAddress.Parse(nic.NetworkFormat).V6;
                nic.SubnetMask = GetPrivateNetworkSubnetMask(settings["PrivateSubnetMask"], v6);
            }
            else
            {
                // standard format
                string[] formatPair = settings["PrivateNetworkFormat"].Split('/');
                nic.NetworkFormat = formatPair[0];
				var v6 = IPAddress.Parse(nic.NetworkFormat).V6;
				nic.SubnetMask = GetPrivateNetworkSubnetMask(formatPair[1], v6);
            }

            nic.SubnetMaskCidr = GetSubnetMaskCidr(nic.SubnetMask);
            nic.DefaultGateway = settings["PrivateDefaultGateway"];
            nic.PreferredNameServer = settings["PrivatePreferredNameServer"];
            nic.AlternateNameServer = settings["PrivateAlternateNameServer"];

            return nic;
        }

        public static ResultObject AddVirtualMachinePrivateIPAddresses(int itemId, bool selectRandom, int addressesNumber, string[] addresses, bool provisionKvp)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "ADD_PRIVATE_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // load network adapter
                NetworkAdapterDetails nic = GetPrivateNetworkAdapterDetails(itemId);

                bool wasEmptyList = (nic.IPAddresses.Length == 0);

                // check IP addresses if they are specified
                List<string> checkResults = CheckPrivateIPAddresses(vm.PackageId, addresses);
                if (checkResults.Count > 0)
                {
                    res.ErrorCodes.AddRange(checkResults);
                    res.IsSuccess = false;
                    TaskManager.CompleteResultTask();
                    return res;
                }

                // load all existing private IP addresses
                List<PrivateIPAddress> ips = GetPackagePrivateIPAddresses(vm.PackageId);

                // sort them
                SortedList<IPAddress, string> sortedIps = GetSortedNormalizedIPAddresses(ips, nic.SubnetMask);

                if (selectRandom)
                {
                    // generate N number of IP addresses
                    addresses = new string[addressesNumber];
                    for (int i = 0; i < addressesNumber; i++)
                        addresses[i] = GenerateNextAvailablePrivateIP(sortedIps, nic.SubnetMask, nic.NetworkFormat);
                }

                PackageContext cntx = PackageController.GetPackageContext(vm.PackageId);
                QuotaValueInfo quota = cntx.Quotas[Quotas.VPS_PRIVATE_IP_ADDRESSES_NUMBER];
                if (quota.QuotaAllocatedValue != -1)
                {
                    int maxAddresses = quota.QuotaAllocatedValue - nic.IPAddresses.Length;

                    if (addresses.Length > maxAddresses)
                    {
                        TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.QUOTA_EXCEEDED_PRIVATE_ADDRESSES_NUMBER + ":" + maxAddresses);
                        return res;
                    }
                }

                // add addresses to database
                foreach (string address in addresses)
                    DataProvider.AddItemPrivateIPAddress(SecurityContext.User.UserId, itemId, address);

                // set primary IP address
                if (wasEmptyList)
                {
                    nic = GetPrivateNetworkAdapterDetails(itemId);
                    if (nic.IPAddresses.Length > 0)
                        SetVirtualMachinePrimaryPrivateIPAddress(itemId, nic.IPAddresses[0].AddressId, false);
                }

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "Private");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.ADD_VIRTUAL_MACHINE_PRIVATE_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        private static List<string> CheckPrivateIPAddresses(int packageId, string[] addresses)
        {
            List<string> codes = new List<string>();

            // check IP addresses if they are specified
            if (addresses != null && addresses.Length > 0)
            {
                // load network adapter
                NetworkAdapterDetails nic = GetPrivateNetworkDetails(packageId);

                foreach (string address in addresses)
                {
                    if (!CheckPrivateIPAddress(nic.SubnetMask, address))
                        codes.Add(VirtualizationErrorCodes.WRONG_PRIVATE_IP_ADDRESS_FORMAT + ":" + address);
                }
            }

            return codes;
        }

        public static ResultObject SetVirtualMachinePrimaryPrivateIPAddress(int itemId, int addressId, bool provisionKvp)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "SET_PRIMARY_PRIVATE_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // call data access layer
                DataProvider.SetItemPrivatePrimaryIPAddress(SecurityContext.User.UserId, itemId, addressId);

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "Private");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.SET_VIRTUAL_MACHINE_PRIMARY_PRIVATE_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static ResultObject DeleteVirtualMachinePrivateIPAddresses(int itemId, int[] addressIds, bool provisionKvp)
        {
            if (addressIds == null)
                throw new ArgumentNullException("addressIds");

            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "DELETE_PRIVATE_IP");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // call data access layer
                foreach (int addressId in addressIds)
                    DataProvider.DeleteItemPrivateIPAddress(SecurityContext.User.UserId, itemId, addressId);

                // send KVP config items
                if(provisionKvp)
                    SendNetworkAdapterKVP(itemId, "Private");
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.DELETE_VIRTUAL_MACHINE_PRIVATE_IP_ADDRESS_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        private static string GenerateNextAvailablePrivateIP(SortedList<IPAddress, string> ips, string subnetMask, string startIPAddress)
        {
            // start IP address
           var startIp = IPAddress.Parse(startIPAddress);
            var mask = IPAddress.Parse(subnetMask);

            var lastAddress = (startIp & ~mask) - 1;
            foreach (var addr in ips.Keys)
            {
                if ((addr - lastAddress) > 1)
                {
                    // it is a gap
                    break;
                }
                else
                {
                    lastAddress = addr;
                }
            }

            var genAddr = lastAddress + 1;

            // convert to IP address
            var ip = startIp & mask | genAddr;
            string genIP = ip.ToString();

            // store in cache
            ips.Add(genAddr, genIP);

            return genIP;
        }

        private static SortedList<IPAddress, string> GetSortedNormalizedIPAddresses(List<PrivateIPAddress> ips, string subnetMask)
        {
            var mask = IPAddress.Parse(subnetMask);
            SortedList<IPAddress, string> sortedIps = new SortedList<IPAddress, string>();
            foreach (PrivateIPAddress ip in ips)
            {
                var addr = ~mask & IPAddress.Parse(ip.IPAddress);
                sortedIps.Add(addr, ip.IPAddress);
            }
            return sortedIps;
        }

		private static string GetPrivateNetworkSubnetMask(string cidr, bool v6) {
			if (v6) return "/" + cidr;
			else return IPAddress.Parse("/" + cidr).ToV4MaskString();
		}

		private static string GetSubnetMaskCidr(string subnetMask) {
			if (String.IsNullOrEmpty(subnetMask))
				return subnetMask;
			var ip = IPAddress.Parse(subnetMask);
			if (ip.V4) {
				int cidr = 32;
				var mask = ip.Address;
				while ((mask & 1) == 0 && cidr > 0) {
					mask >>= 1;
					cidr -= 1;
				}
				return cidr.ToString();
			} else {
				return ip.Cidr.ToString();
			}
		}
		
        private static bool CheckPrivateIPAddress(string subnetMask, string ipAddress)
        {
            var mask = IPAddress.Parse(subnetMask);
            var ip = IPAddress.Parse(ipAddress);

            return ((mask & ip) == mask);
        }
        #endregion

        #region Virtual Machine Permissions
        public static List<VirtualMachinePermission> GetVirtualMachinePermissions(int itemId)
        {
            List<VirtualMachinePermission> result = new List<VirtualMachinePermission>();
            return result;
        }

        public static int UpdateVirtualMachineUserPermissions(int itemId, VirtualMachinePermission[] permissions)
        {
            // VPS - UPDATE_PERMISSIONS
            return 0;
        }
        #endregion

        #region Virtual Switches
        public static VirtualSwitch[] GetExternalSwitches(int serviceId, string computerName)
        {
            VirtualizationServer vs = new VirtualizationServer();
            ServiceProviderProxy.Init(vs, serviceId);
            return vs.GetExternalSwitches(computerName);
        }
        #endregion

        #region Tools
        public static ResultObject DeleteVirtualMachine(int itemId, bool saveFiles, bool exportVps, string exportPath)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "DELETE");

            // log item info
            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // get proxy
                VirtualizationServer vs = GetVirtualizationProxy(vm.ServiceId);

                // check VM state
                VirtualMachine vps = vs.GetVirtualMachine(vm.VirtualMachineId);

                JobResult result = null;

                if (vps != null)
                {
                    #region turn off machine (if required)

                    // stop virtual machine
                    if (vps.State != VirtualMachineState.Off)
                    {
                        TaskManager.Write("VPS_DELETE_TURN_OFF");
                        result = vs.ChangeVirtualMachineState(vm.VirtualMachineId, VirtualMachineRequestedState.TurnOff);
                        // check result
                        if (result.ReturnValue != ReturnCode.JobStarted)
                        {
                            LogReturnValueResult(res, result);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }

                        // wait for completion
                        if (!JobCompleted(vs, result.Job))
                        {
                            LogJobResult(res, result.Job);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }
                    }
                    #endregion

                    #region export machine
                    if (exportVps && !String.IsNullOrEmpty(exportPath))
                    {
                        TaskManager.Write("VPS_DELETE_EXPORT");
                        result = vs.ExportVirtualMachine(vm.VirtualMachineId, exportPath);

                        // check result
                        if (result.ReturnValue != ReturnCode.JobStarted)
                        {
                            LogReturnValueResult(res, result);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }

                        // wait for completion
                        if (!JobCompleted(vs, result.Job))
                        {
                            LogJobResult(res, result.Job);
                            TaskManager.CompleteResultTask(res);
                            return res;
                        }
                    }
                    #endregion

                    #region delete machine
                    TaskManager.Write("VPS_DELETE_DELETE");
                    result = vs.DeleteVirtualMachine(vm.VirtualMachineId);

                    // check result
                    if (result.ReturnValue != ReturnCode.JobStarted)
                    {
                        LogReturnValueResult(res, result);
                        TaskManager.CompleteResultTask(res);
                        return res;
                    }

                    // wait for completion
                    if (!JobCompleted(vs, result.Job))
                    {
                        LogJobResult(res, result.Job);
                        TaskManager.CompleteResultTask(res);
                        return res;
                    }
                    #endregion
                }

                #region delete files
                if (!saveFiles)
                {
                    TaskManager.Write("VPS_DELETE_FILES", vm.RootFolderPath);
                    try
                    {
                        vs.DeleteRemoteFile(vm.RootFolderPath);
                    }
                    catch (Exception ex)
                    {
                        res.ErrorCodes.Add(VirtualizationErrorCodes.DELETE_VM_FILES_ERROR + ": " + ex.Message);
                    }
                }
                #endregion

                // delete meta item
                PackageController.DeletePackageItem(itemId);
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.DELETE_ERROR, ex);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static int ReinstallVirtualMachine(int itemId, string adminPassword, bool preserveVirtualDiskFiles,
            bool saveVirtualDisk, bool exportVps, string exportPath)
        {
            // VPS - REINSTALL
            return 0;
        }
        #endregion

        #region Help
        public static string GetVirtualMachineSummaryText(int itemId, bool emailMode, bool creation)
        {
            // load item
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);

            // load user info
            UserInfo user = PackageController.GetPackageOwner(vm.PackageId);

            // get letter settings
            UserSettings settings = UserController.GetUserSettings(user.UserId, UserSettings.VPS_SUMMARY_LETTER);

            string settingName = user.HtmlMail ? "HtmlBody" : "TextBody";
            string body = settings[settingName];
            if (String.IsNullOrEmpty(body))
                return null;

            string result = EvaluateVirtualMachineTemplate(itemId, emailMode, creation, body);
            return user.HtmlMail ? result : result.Replace("\n", "<br/>");
        }

        public static ResultObject SendVirtualMachineSummaryLetter(int itemId, string to, string bcc, bool creation)
        {
            ResultObject res = new ResultObject();

            // load service item
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
            {
                res.ErrorCodes.Add(VirtualizationErrorCodes.CANNOT_FIND_VIRTUAL_MACHINE_META_ITEM);
                return res;
            }

            #region Check account and space statuses
            // check account
            if (!SecurityContext.CheckAccount(res, DemandAccount.NotDemo | DemandAccount.IsActive))
                return res;

            // check package
            if (!SecurityContext.CheckPackage(res, vm.PackageId, DemandPackage.IsActive))
                return res;
            #endregion

            // start task
            res = TaskManager.StartResultTask<ResultObject>("VPS", "SEND_SUMMARY_LETTER");

            TaskManager.ItemId = vm.Id;
            TaskManager.ItemName = vm.Name;
            TaskManager.PackageId = vm.PackageId;

            try
            {
                // load user info
                UserInfo user = PackageController.GetPackageOwner(vm.PackageId);

                // get letter settings
                UserSettings settings = UserController.GetUserSettings(user.UserId, UserSettings.VPS_SUMMARY_LETTER);

                string from = settings["From"];
                if (bcc == null)
                    bcc = settings["CC"];
                string subject = settings["Subject"];
                string body = user.HtmlMail ? settings["HtmlBody"] : settings["TextBody"];
                bool isHtml = user.HtmlMail;

                MailPriority priority = MailPriority.Normal;
                if (!String.IsNullOrEmpty(settings["Priority"]))
                    priority = (MailPriority)Enum.Parse(typeof(MailPriority), settings["Priority"], true);

                if (String.IsNullOrEmpty(body))
                {
                    TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.SUMMARY_TEMPLATE_IS_EMPTY);
                    return res;
                }

                // load user info
                if (to == null)
                    to = user.Email;

                subject = EvaluateVirtualMachineTemplate(itemId, true, creation, subject);
                body = EvaluateVirtualMachineTemplate(itemId, true, creation, body);

                // send message
                int result = MailHelper.SendMessage(from, to, bcc, subject, body, priority, isHtml);

                if (result != 0)
                {
                    TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.SEND_SUMMARY_LETTER_CODE + ":" + result);
                    TaskManager.WriteWarning("VPS_SEND_SUMMARY_LETTER_ERROR_CODE", result.ToString());
                    return res;
                }
            }
            catch (Exception ex)
            {
                TaskManager.CompleteResultTask(res, VirtualizationErrorCodes.SEND_SUMMARY_LETTER, ex);
                TaskManager.WriteWarning("VPS_SEND_SUMMARY_LETTER_ERROR", ex.Message);
                return res;
            }

            TaskManager.CompleteResultTask();
            return res;
        }

        public static string EvaluateVirtualMachineTemplate(int itemId, bool emailMode, bool creation, string template)
        {
            Hashtable items = new Hashtable();

            // load machine details
            VirtualMachine vm = GetVirtualMachineByItemId(itemId);
            if (vm == null)
                throw new Exception("VPS with the specified ID was not found.");

            // space info
            PackageInfo package = PackageController.GetPackage(vm.PackageId);
            items["space"] = package;

            // user info
            items["user"] = PackageController.GetPackageOwner(vm.PackageId);

            // VM item
            items["vm"] = vm;

            // load external NIC
            items["external_nic"] = GetExternalNetworkAdapterDetails(itemId);

            // load private NIC
            items["private_nic"] = GetPrivateNetworkAdapterDetails(itemId);

            // load private NIC
            items["management_nic"] = GetManagementNetworkAdapterDetails(itemId);

            // load service settings
            StringDictionary settings = ServerController.GetServiceSettings(vm.ServiceId);

            foreach (string key in settings.Keys)
                items[key] = settings[key];

            // service items
            items["email"] = emailMode;
            items["creation"] = creation;

            // evaluate template
            return PackageController.EvaluateTemplate(template, items);
        }
        #endregion

        #region Helper methods
        private static int GetServiceId(int packageId)
        {
            int serviceId = PackageController.GetPackageServiceId(packageId, ResourceGroups.VPS);
            return serviceId;
        }

        private static VirtualizationServer GetVirtualizationProxyByPackageId(int packageId)
        {
            // get service
            int serviceId = GetServiceId(packageId);

            return GetVirtualizationProxy(serviceId);
        }
        
        private static VirtualizationServer GetVirtualizationProxy(int serviceId)
        {
            VirtualizationServer ws = new VirtualizationServer();
            ServiceProviderProxy.Init(ws, serviceId);
            return ws;
        }

        public static VirtualMachine GetVirtualMachineByItemId(int itemId)
        {
            VirtualMachine vm = (VirtualMachine)PackageController.GetPackageItem(itemId);
            if (vm == null)
                return null;

            // host name
            int dotIdx = vm.Name.IndexOf(".");
            if (dotIdx > -1)
            {
                vm.Hostname = vm.Name.Substring(0, dotIdx);
                vm.Domain = vm.Name.Substring(dotIdx + 1);
            }
            else
            {
                vm.Hostname = vm.Name;
                vm.Domain = "";
            }

            // check if task was aborted during provisioning
            if (!String.IsNullOrEmpty(vm.CurrentTaskId)
                && TaskManager.GetTask(vm.CurrentTaskId) == null)
            {
                // set to error
                vm.CurrentTaskId = null;
                vm.ProvisioningStatus = VirtualMachineProvisioningStatus.Error;
                PackageController.UpdatePackageItem(vm);
            }

            vm.AdministratorPassword = CryptoUtils.Decrypt(vm.AdministratorPassword);
            return vm;
        }

        private static void LogReturnValueResult(ResultObject res, JobResult job)
        {
            res.ErrorCodes.Add(VirtualizationErrorCodes.JOB_START_ERROR + ":" + job.ReturnValue);
        }

        private static void LogJobResult(ResultObject res, ConcreteJob job)
        {
            res.ErrorCodes.Add(VirtualizationErrorCodes.JOB_FAILED_ERROR + ":" + job.ErrorDescription);
        }

        private static bool JobCompleted(VirtualizationServer vs, ConcreteJob job)
        {
            TaskManager.IndicatorMaximum = 100;
            bool jobCompleted = true;

            while (job.JobState == ConcreteJobState.Starting ||
                job.JobState == ConcreteJobState.Running)
            {
                System.Threading.Thread.Sleep(1000);
                job = vs.GetJob(job.Id);
                TaskManager.IndicatorCurrent = job.PercentComplete;
            }

            if (job.JobState != ConcreteJobState.Completed)
            {
                jobCompleted = false;
            }

            TaskManager.IndicatorCurrent = 0; // reset indicator

            return jobCompleted;
        }

        private static string GenerateMacAddress()
        {
            return MS_MAC_PREFIX + Utils.GetRandomHexString(3);
        }

        #endregion
    }
}