/* Copyright (c) Citrix Systems, Inc. 
 * 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. 
 * 
 * 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.Collections.Generic;
using System.Text;
using System.Linq;
using XenAPI;
using System.Windows.Forms;
using XenAdmin.Actions;
using XenAdmin.Core;
using XenAdmin.Commands.Controls;
using XenAdmin.Dialogs;


namespace XenAdmin.Commands
{
    class DeleteVirtualDiskCommand : Command
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        
        /// <summary>
        /// Allows auto unplug and delete on running VMs
        /// </summary>
        public bool AllowRunningVMDelete = false;

        /// <summary>
        /// Allows deletion of the VDI when multiple VMs are using this VDI. If the VMs are running you also need to set AllowRunningVMDelete
        /// </summary>
        public bool AllowMultipleVBDDelete = false;

        public DeleteVirtualDiskCommand(IMainWindow mainWindow, IEnumerable<SelectedItem> selection)
            : base(mainWindow, selection)
        {
            
        }

        public override string ContextMenuText
        {
            get
            {
                var selection = GetSelection();
                if (selection.Count > 1)
                    return Messages.MAINWINDOW_DELETE_OBJECTS;

                return selection.AsXenObjects<VDI>().All(v => v.is_a_snapshot)
                    ? Messages.DELETE_SNAPSHOT_MENU_ITEM
                    : Messages.DELETE_VIRTUAL_DISK;
            }
        }


        protected override bool CanExecuteCore(SelectedItemCollection selection)
        {
            return selection.AllItemsAre<VDI>(CanExecute);
        }

        protected override bool ConfirmationRequired
        {
            get
            {
                // We always do some sort of confirmation for this delete
                return true;
            }
        }

        protected override string ConfirmationDialogText
        {
            get
            {
                if (!NeedMultipleWarnings)
                {
                    // In the single warning type case we use the in built confirmation dialog to show a single warning
                    // if there are mixed vdi types then we will use the multiple warning dialog in confirm() override
                    SelectedItemCollection selectedItems = GetSelection();
                    VDI vdi = selectedItems[0].XenObject as VDI;
                    SR sr = vdi.Connection.Resolve<SR>(vdi.SR);
                    bool single = selectedItems.Count == 1;

                    var typ = vdi.VDIType();
                    switch (typ)
                    {
                        case VDI.FriendlyType.SNAPSHOT:
                            return single ? Messages.MESSAGEBOX_DELETE_SNAPSHOT : Messages.MESSAGEBOX_DELETE_SNAPSHOT_MULTIPLE;
                        case VDI.FriendlyType.ISO:
                            return single ? string.Format(Messages.MESSAGEBOX_DELETE_ISO, Helpers.GetName(vdi)) : Messages.MESSAGEBOX_DELETE_ISO_MULTIPLE;
                        case VDI.FriendlyType.SYSTEM_DISK:
                            return single ? Messages.MESSAGEBOX_DELETE_SYS_DISK : Messages.MESSAGEBOX_DELETE_SYS_DISK_MULTIPLE;
                        case VDI.FriendlyType.VIRTUAL_DISK:
                            return single ? Messages.MESSAGEBOX_DELETE_VD : Messages.MESSAGEBOX_DELETE_VD_MULTIPLE;
                        case VDI.FriendlyType.NONE:
                            return "";
                    }
                }
                return base.ConfirmationDialogText;
            }
        }

        protected override string ConfirmationDialogTitle
        {
            get
            {
                if (!NeedMultipleWarnings)
                {
                    // In the single warning type case we use the in built confirmation dialog to show a single warning
                    // if there are mixed vdi types then we will use the multiple warning dialog in confirm() override
                    SelectedItemCollection selectedItems = GetSelection();
                    VDI vdi = selectedItems[0].XenObject as VDI;
                    bool single = selectedItems.Count == 1;

                    var typ = vdi.VDIType();
                    switch (typ)
                    {
                        case VDI.FriendlyType.SNAPSHOT:
                            return single ? Messages.MESSAGEBOX_DELETE_SNAPSHOT_TITLE : Messages.MESSAGEBOX_DELETE_SNAPSHOTS_TITLE_MULTIPLE;
                        case VDI.FriendlyType.ISO:
                            return single ? Messages.MESSAGEBOX_DELETE_ISO_TITLE : Messages.MESSAGEBOX_DELETE_ISO_TITLE_MULTIPLE;
                        case VDI.FriendlyType.SYSTEM_DISK:
                            return single ? Messages.MESSAGEBOX_DELETE_SYS_DISK_TITLE : Messages.MESSAGEBOX_DELETE_SYS_DISK_TITLE_MULTIPLE;
                        case VDI.FriendlyType.VIRTUAL_DISK:
                            return single ? Messages.MESSAGEBOX_DELETE_VD_TITLE : Messages.MESSAGEBOX_DELETE_VD_TITLE_MUTLIPLE;
                        case VDI.FriendlyType.NONE:
                            return "";     
                    }
                }
                return base.ConfirmationDialogTitle;
            }
        }

        private bool? _needMultipleWarnings = null;
        private bool NeedMultipleWarnings
        {
            get
            {
                // While just O(n) it does cast an entire list, so we cache the first result.
                if (_needMultipleWarnings.HasValue)
                    return _needMultipleWarnings.Value;

                _needMultipleWarnings = false;
                SelectedItemCollection selection = GetSelection();
                VDI.FriendlyType current = VDI.FriendlyType.NONE;
                VDI.FriendlyType previous = VDI.FriendlyType.NONE;
                if (selection.Count > 1)
                {
                    for (int i = 0; i < selection.Count; i++)
                    {
                        VDI v = selection[i].XenObject as VDI;
                        if (v == null)
                            current = VDI.FriendlyType.NONE;
                        else
                            current = v.VDIType();

                        if (i > 0 && current != previous)
                        {
                            _needMultipleWarnings = true;
                            break;
                        }
                        previous = current;
                    }
                }
                return _needMultipleWarnings.Value;
            }
        }

        protected override bool Confirm()
        {
            if (Program.RunInAutomatedTestMode)
                return true;

            if (NeedMultipleWarnings)
            {
                MultipleWarningDialog warningDialog = new MultipleWarningDialog(
                    Messages.MESSAGEBOX_DELETE_VD_TITLE_MUTLIPLE,
                    Messages.MULTI_VDI_DELETE_WARNING,
                    Messages.DELETE_ALL_BUTTON_LABEL);

                SelectedItemCollection selectedItems = GetSelection();
                List<VDI> snapshots = new List<VDI>();
                List<VDI> isos = new List<VDI>();
                List<VDI> systemVDisks = new List<VDI>();
                List<VDI> virtualDisks = new List<VDI>();
                foreach (VDI vdi in selectedItems.AsXenObjects<VDI>())
                {
                    var typ = vdi.VDIType();
                    switch (typ)
                    {
                        case VDI.FriendlyType.SNAPSHOT:
                            snapshots.Add(vdi); 
                            break;
                        case VDI.FriendlyType.ISO:
                            isos.Add(vdi); 
                            break;
                        case VDI.FriendlyType.SYSTEM_DISK:
                            systemVDisks.Add(vdi); 
                            break;
                        case VDI.FriendlyType.VIRTUAL_DISK:
                            virtualDisks.Add(vdi); 
                            break;
                        case VDI.FriendlyType.NONE:
                            break;
                    }
                }
                if (snapshots.Count == 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_SNAPSHOT_TITLE,
                        Messages.WARNING_DELETE_SNAPSHOT,
                        snapshots.ConvertAll<IXenObject>(delegate(VDI v){return (IXenObject)v;}));
                }
                else if (snapshots.Count > 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_SNAPSHOTS_TITLE_MULTIPLE,
                        Messages.WARNING_DELETE_SNAPSHOT_MULTIPLE,
                        snapshots.ConvertAll<IXenObject>(delegate(VDI v){return (IXenObject)v;}));
                }

                if (isos.Count == 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_ISO_TITLE,
                        Messages.WARNING_DELETE_ISO,
                        isos.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }
                else if (isos.Count > 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_ISO_TITLE_MULTIPLE,
                        Messages.WARNING_DELETE_ISO_MULTIPLE,
                        isos.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }

                if (systemVDisks.Count == 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_SYS_DISK_TITLE,
                        Messages.WARNING_DELETE_SYS_DISK,
                        systemVDisks.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }
                else if (systemVDisks.Count > 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_SYS_DISK_TITLE_MULTIPLE,
                        Messages.WARNING_DELETE_SYS_DISK_MULTIPLE,
                        systemVDisks.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }

                if (virtualDisks.Count == 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_VD_TITLE,
                        Messages.WARNING_DELETE_VD,
                        virtualDisks.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }
                else if (virtualDisks.Count > 1)
                {
                    warningDialog.AddWarningMessage(
                        Messages.MESSAGEBOX_DELETE_VD_TITLE_MUTLIPLE,
                        Messages.WARNING_DELETE_VD_MULTIPLE,
                        virtualDisks.ConvertAll<IXenObject>(delegate(VDI v) { return (IXenObject)v; }));
                }
                return warningDialog.ShowDialog(Parent) == DialogResult.Yes;
            }
            else
            {
                return base.Confirm();
            }
        }

        protected bool CanExecute(VDI vdi)
        {
            if (vdi == null)
                return false;
            SR sr = vdi.Connection.Resolve<SR>(vdi.SR);
            if (sr == null)
                return false;
            if (vdi.Locked)
                return false;
            if (sr.Physical())
                return false;
            if (sr.IsToolsSR())
                return false;
            if (vdi.IsUsedByHA())
            {
                return false;
            }
            List<VBD> vbds = vdi.Connection.ResolveAll<VBD>(vdi.VBDs);
            if (vbds.Count > 1 && !AllowMultipleVBDDelete)
                return false;
            foreach (VBD vbd in vbds)
            {
                VM vm = vdi.Connection.Resolve<VM>(vbd.VM);

                if (vdi.type == vdi_type.system)
                {
                    
                    if (vm.power_state == vm_power_state.Running)
                        return false;
                }
                if (vbd.Locked)
                    return false;
                if (vbd.currently_attached)
                {
                    //Check if we can unplug
                    DeactivateVBDCommand cmd = new DeactivateVBDCommand(Program.MainWindow, vbd);
                    if (!AllowRunningVMDelete || !cmd.CanExecute())
                        return false;
                }
            }
            if (sr.HBALunPerVDI())
                return true;
            if (!vdi.allowed_operations.Contains(vdi_operations.destroy))
            {
                if (AllowRunningVMDelete)
                {
                    // We deliberately DONT call allowed operations because we assume we know better :)
                    // Xapi will think we can't delete because VBDs are plugged. We are going to unplug them.

                    // Known risks of this method that will make us fail because we are disrespecting xapi:
                    // - someone else is calling a delete on this vdi already, altering the allowed ops
                    // - the storage manager cannot perform a delete on the SR due to drivers

                    return true;
                }
                return false;
            }
            return true;
        }

        protected override string GetCantExecuteReasonCore(SelectedItem item)
        {
            VDI vdi = item.XenObject as VDI;
            if (vdi == null)
                return base.GetCantExecuteReasonCore(item);

            SR sr = vdi.Connection.Resolve<SR>(vdi.SR);
            if (sr == null)
                return Messages.SR_COULD_NOT_BE_CONTACTED;

            VDI.FriendlyType vdiType = vdi.VDIType();
           
            if (vdi.Locked)
                return vdiType == VDI.FriendlyType.SNAPSHOT ? Messages.CANNOT_DELETE_SNAPSHOT_IN_USE
                    : vdiType == VDI.FriendlyType.ISO ? Messages.CANNOT_DELETE_ISO_IN_USE
                    : Messages.CANNOT_DELETE_VD_IN_USE;

            if (sr.Physical())
                return FriendlyErrorNames.VDI_IS_A_PHYSICAL_DEVICE;

            if (sr.IsToolsSR())
                return Messages.CANNOT_DELETE_TOOLS_SR;

            if (vdi.IsUsedByHA())
                return Messages.CANNOT_DELETE_HA_VD;

            if (vdi.IsMetadataForDR())
                return Messages.CANNOT_DELETE_DR_VD;

            List<VBD> vbds = vdi.Connection.ResolveAll<VBD>(vdi.VBDs);
            if (vbds.Count > 1 && !AllowMultipleVBDDelete)
                return Messages.CANNOT_DELETE_VDI_MULTIPLE_VBDS;

            foreach (VBD vbd in vbds)
            {
                VM vm = vdi.Connection.Resolve<VM>(vbd.VM);

                if (vdiType == VDI.FriendlyType.SYSTEM_DISK)
                {
                  

                    if (vm.power_state == vm_power_state.Running)
                        return string.Format(
                            Messages.CANNOT_DELETE_IN_USE_SYS_VD,
                            Helpers.GetName(vm).Ellipsise(20));
                }
                if (vbd.Locked)
                    return vdiType == VDI.FriendlyType.SNAPSHOT ? Messages.CANNOT_DELETE_SNAPSHOT_IN_USE
                    : vdiType == VDI.FriendlyType.ISO ? Messages.CANNOT_DELETE_ISO_IN_USE
                    : Messages.CANNOT_DELETE_VD_IN_USE;

                if (vbd.currently_attached)
                {
                    if (!AllowRunningVMDelete)
                    {
                        return string.Format(Messages.CANNOT_DELETE_VDI_ACTIVE_ON,
                            Helpers.GetName(vm).Ellipsise(20));
                    }
                    DeactivateVBDCommand cmd = new DeactivateVBDCommand(Program.MainWindow, vbd);
                    if (!cmd.CanExecute())
                    {
                        var reasons = cmd.GetCantExecuteReasons();
                        return reasons.Count > 0
                            ? string.Format(Messages.CANNOT_DELETE_CANNOT_DEACTIVATE_REASON,
                                Helpers.GetName(vm).Ellipsise(20), reasons.ElementAt(0).Value)
                            : Messages.UNKNOWN;
                    }
                }
            }

            // This is a necessary final check, there are other blocking reasons non covered in this method
            // Known examples:
            // - someone else is calling a delete on this vdi already, altering the allowed ops
            // - the storage manager cannot perform a delete on the SR due to drivers
            if (!vdi.allowed_operations.Contains(vdi_operations.destroy))
                return vdiType == VDI.FriendlyType.SNAPSHOT ? Messages.CANNOT_DELETE_SNAPSHOT_GENERIC
                    : vdiType == VDI.FriendlyType.ISO ? Messages.CANNOT_DELETE_ISO_GENERIC
                    : Messages.CANNOT_DELETE_VD_GENERIC;

            return base.GetCantExecuteReasonCore(item);
        }

        protected override XenAdmin.Dialogs.CommandErrorDialog GetErrorDialogCore(IDictionary<SelectedItem, string> cantExecuteReasons)
        {
            return new CommandErrorDialog(Messages.ERROR_DESTROYING_STORAGE_ITEMS_TITLE, Messages.ERROR_DESTROYING_STORAGE_ITEMS_MESSAGE, cantExecuteReasons);
        }

        protected override void ExecuteCore(SelectedItemCollection selection)
        {
            List<AsyncAction> actionsToComplete = new List<AsyncAction>();
            List<VM> deletedVMSnapshots = new List<VM>();

            foreach (VDI vdi in selection.AsXenObjects<VDI>())
            {
                if (vdi.Locked || !vdi.Show(XenAdmin.Properties.Settings.Default.ShowHiddenVMs))
                    continue;

                actionsToComplete.AddRange(getDestroyVDIAction(vdi, deletedVMSnapshots));
            }

            if (actionsToComplete.Count == 0)
                return;

            if (actionsToComplete.Count > 1)
                RunMultipleActions(actionsToComplete, Messages.ACTION_DELETING_MULTIPLE_STORAGE_ITEMS_TITLE, Messages.ACTION_DELETING_MULTIPLE_STORAGE_ITEMS_STATUS, Messages.COMPLETED, true);
            else
                actionsToComplete[0].RunAsync();
        }

        private List<AsyncAction> getDestroyVDIAction(VDI vdi, List<VM> deletedVMSnapshots)
        {
            List<AsyncAction> actions = new List<AsyncAction>();

            // Destroy the entire snapshot if it exists.  Else destroy disk
            if (vdi.is_a_snapshot && vdi.GetVMs().Count >= 1)
            {
                foreach (VM vm in vdi.GetVMs())
                {
                    if (!vm.is_a_snapshot || deletedVMSnapshots.Contains(vm))
                        continue;

                    AsyncAction action = new VMSnapshotDeleteAction(vm);
                    actions.Add(action);
                    deletedVMSnapshots.Add(vm);
                }
            }
            else
            {
                SR sr = vdi.Connection.Resolve(vdi.SR);
                if (sr == null)
                {
                    // Nothing we can do here, but this should have been caught in the getcantexecutereason method and prompted
                    return actions;
                }
                DestroyDiskAction a = new DestroyDiskAction(vdi);
                a.AllowRunningVMDelete = AllowRunningVMDelete;
                actions.Add(a);
            }

            return actions;
        }
    }
}
