/* 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.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using XenAdmin.Actions;
using XenAdmin.Core;
using XenAdmin.Dialogs;
using XenAPI;


namespace XenAdmin.Commands
{
    /// <summary>
    /// A Command for adding a host to a pool. This Command does not use the current selection for specifying the host and pool.
    /// The host and pool are set in the constructor for this class.
    /// </summary>
    internal class AddHostToPoolCommand : Command
    {
        private readonly List<Host> _hosts;
        private readonly Pool _pool;
        private readonly bool _confirm;

        /// <summary>
        /// Initializes a new instance of the <see cref="AddHostToPoolCommand"/> class.
        /// </summary>
        /// <param name="mainWindow">The main window interface. It can be found at Program.MainWindow.CommandInterface.</param>
        /// <param name="hosts">The hosts which are to be added to the pool.</param>
        /// <param name="pool">The pool the host should be added to.</param>
        /// <param name="confirm">if set to <c>true</c> a confirmation dialog is shown.</param>
        public AddHostToPoolCommand(IMainWindow mainWindow, IEnumerable<Host> hosts, Pool pool, bool confirm)
            : base(mainWindow)
        {
            Util.ThrowIfParameterNull(hosts, "hosts");
            Util.ThrowIfParameterNull(pool, "pool");
            _hosts = new List<Host>(hosts);
            _confirm = confirm;
            _pool = pool;
        }

        protected override void ExecuteCore(SelectedItemCollection selection)
        {
            Dictionary<SelectedItem, string> reasons = new Dictionary<SelectedItem, string>();
            foreach (Host host in _hosts)
            {
                PoolJoinRules.Reason reason = PoolJoinRules.CanJoinPool(host.Connection, _pool.Connection, true, true, true);
                if (reason != PoolJoinRules.Reason.Allowed)
                    reasons[new SelectedItem(host)] = PoolJoinRules.ReasonMessage(reason);
            }

            if (reasons.Count > 0)
            {
                string title = Messages.ERROR_DIALOG_ADD_TO_POOL_TITLE;
                string text = string.Format(Messages.ERROR_DIALOG_ADD_TO_POOL_TEXT, Helpers.GetName(_pool).Ellipsise(500));

                new CommandErrorDialog(title, text, reasons).ShowDialog(Parent);
                return;
            }

            if (_confirm && !ShowConfirmationDialog())
            {
                // Bail out if the user doesn't want to continue.
                return;
            }

            if (!Helpers.IsConnected(_pool))
            {
                string message = _hosts.Count == 1
                                     ? string.Format(Messages.ADD_HOST_TO_POOL_DISCONNECTED_POOL,
                                                     Helpers.GetName(_hosts[0]).Ellipsise(500), Helpers.GetName(_pool).Ellipsise(500))
                                     : string.Format(Messages.ADD_HOST_TO_POOL_DISCONNECTED_POOL_MULTIPLE,
                                                     Helpers.GetName(_pool).Ellipsise(500));

                using (var dlg = new ThreeButtonDialog(
                    new ThreeButtonDialog.Details(SystemIcons.Error, message, Messages.XENCENTER)))
                {
                    dlg.ShowDialog(Parent);
                }
                return;
            }

            // Check supp packs and warn
            List<string> badSuppPacks = PoolJoinRules.HomogeneousSuppPacksDiffering(_hosts, _pool);
            if (!HelpersGUI.GetPermissionFor(badSuppPacks, sp => true,
                Messages.ADD_HOST_TO_POOL_SUPP_PACK, Messages.ADD_HOST_TO_POOL_SUPP_PACKS, false, "PoolJoinSuppPacks"))
            {
                return;
            }

            // Are there any hosts which are forbidden from masking their CPUs for licensing reasons?
            // If so, we need to show upsell.
            Host master = Helpers.GetMaster(_pool);
            if (null != _hosts.Find(host =>
                !PoolJoinRules.CompatibleCPUs(host, master, false) &&
                Helpers.FeatureForbidden(host, Host.RestrictCpuMasking) &&
                !PoolJoinRules.FreeHostPaidMaster(host, master, false)))  // in this case we can upgrade the license and then mask the CPU
            {
                using (var  dlg = new UpsellDialog(HiddenFeatures.LinkLabelHidden ? Messages.UPSELL_BLURB_CPUMASKING : Messages.UPSELL_BLURB_CPUMASKING + Messages.UPSELL_BLURB_CPUMASKING_MORE,
                                                    InvisibleMessages.UPSELL_LEARNMOREURL_CPUMASKING))
                    dlg.ShowDialog(Parent);
                return;
            }

            // Get permission for any fix-ups: 1) Licensing free hosts; 2) CPU masking 3) Ad configuration 4) CPU feature levelling (Dundee or higher only)
            // (We already know that these things are fixable because we have been through CanJoinPool() above).
            if (!HelpersGUI.GetPermissionFor(_hosts, host => PoolJoinRules.FreeHostPaidMaster(host, master, false),
                Messages.ADD_HOST_TO_POOL_LICENSE_MESSAGE, Messages.ADD_HOST_TO_POOL_LICENSE_MESSAGE_MULTIPLE, true, "PoolJoinRelicensing")
                ||
                !HelpersGUI.GetPermissionFor(_hosts, host => !PoolJoinRules.CompatibleCPUs(host, master, false),
                Messages.ADD_HOST_TO_POOL_CPU_MASKING_MESSAGE, Messages.ADD_HOST_TO_POOL_CPU_MASKING_MESSAGE_MULTIPLE, true, "PoolJoinCpuMasking")
                ||
                !HelpersGUI.GetPermissionFor(_hosts, host => !PoolJoinRules.CompatibleAdConfig(host, master, false),
                Messages.ADD_HOST_TO_POOL_AD_MESSAGE, Messages.ADD_HOST_TO_POOL_AD_MESSAGE_MULTIPLE, true, "PoolJoinAdConfiguring")  
                ||
                !HelpersGUI.GetPermissionForCpuFeatureLevelling(_hosts, _pool))
            {
                return;
            }

            MainWindowCommandInterface.SelectObjectInTree(_pool);

            List<AsyncAction> actions = new List<AsyncAction>();
            foreach (Host host in _hosts)
            {
                string opaque_ref = host.opaque_ref;
                AddHostToPoolAction action = new AddHostToPoolAction(_pool, host, GetAdPrompt, NtolDialog, ApplyLicenseEditionCommand.ShowLicensingFailureDialog);
                action.Completed += s => Program.ShowObject(opaque_ref);
                actions.Add(action);

                // hide connection. If the action fails, re-show it.
                Program.HideObject(opaque_ref);
            }

            RunMultipleActions(actions, string.Format(Messages.ADDING_SERVERS_TO_POOL, _pool.Name), Messages.POOLCREATE_ADDING, Messages.POOLCREATE_ADDED, true);
        }

        protected override bool CanExecuteCore(SelectedItemCollection selection)
        {
            if (_hosts.Count > 0)
            {
                foreach (Host host in _hosts)
                {
                    // only allowed to add standalone hosts.
                    if (Helpers.GetPool(host.Connection) != null || host.RestrictPooling)
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }

        protected override string ConfirmationDialogText
        {
            get
            {
                if (_hosts.Count == 1)
                {
                    return string.Format(Messages.MAINWINDOW_CONFIRM_MOVE_TO_POOL, _hosts[0].Name.Ellipsise(500), _pool.Name.Ellipsise(500));
                }
                else if (_hosts.Count > 1)
                {
                    return string.Format(Messages.MAINWINDOW_CONFIRM_MOVE_TO_POOL_MULTIPLE, _pool.Name.Ellipsise(500));
                }
                return null;
            }
        }

        protected override string ConfirmationDialogTitle
        {
            get
            {
                return Messages.POOLCREATE_ADDING;
            }
        }

        public static PoolAbstractAction.AdUserAndPassword GetAdPrompt(Host poolMaster)
        {
            AdPasswordPrompt adPrompt = new AdPasswordPrompt(true, poolMaster.external_auth_service_name);

            Program.Invoke(Program.MainWindow, delegate
                                                   {
                                                       if (adPrompt.ShowDialog(Program.MainWindow) == DialogResult.Cancel)
                                                           throw new CancelledException();
                                                   });
            return new PoolAbstractAction.AdUserAndPassword(adPrompt.Username, adPrompt.Password);
        }

        public static bool EnableNtolDialog(Pool pool, Host host, long currentNtol, long max)
        {
            bool doit = false;
            Program.Invoke(Program.MainWindow, delegate()
            {
                string poolName = Helpers.GetName(pool).Ellipsise(500);
                string hostName = Helpers.GetName(host).Ellipsise(500);
                string msg = string.Format(Messages.HA_HOST_ENABLE_NTOL_RAISE_QUERY, poolName, hostName, currentNtol, max);
                using (var dlg = new ThreeButtonDialog(
                        new ThreeButtonDialog.Details(null, msg, Messages.HIGH_AVAILABILITY),
                        ThreeButtonDialog.ButtonYes,
                        new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)))
                {
                    if (dlg.ShowDialog(Program.MainWindow) == DialogResult.Yes)
                    {
                        doit = true;
                    }
                }
            });
            return doit;
        }

        public static bool NtolDialog(HostAbstractAction action, Pool pool, long currentNtol, long targetNtol)
        {
            bool cancel = false;
            Program.Invoke(Program.MainWindow, delegate()
            {
                string poolName = Helpers.GetName(pool).Ellipsise(500);
                string hostName = Helpers.GetName(action.Host).Ellipsise(500);

                string msg;
                if (targetNtol == 0)
                {
                    string f;
                    if (action is EvacuateHostAction)
                    {
                        f = Messages.HA_HOST_DISABLE_NTOL_ZERO;
                    }
                    else if (action is RebootHostAction)
                    {
                        f = Messages.HA_HOST_REBOOT_NTOL_ZERO;
                    }
                    else
                    {
                        f = Messages.HA_HOST_SHUTDOWN_NTOL_ZERO;
                    }

                    msg = string.Format(f, poolName, hostName);
                }
                else
                {
                    string f;
                    if (action is EvacuateHostAction)
                    {
                        f = Messages.HA_HOST_DISABLE_NTOL_DROP;
                    }
                    else if (action is RebootHostAction)
                    {
                        f = Messages.HA_HOST_REBOOT_NTOL_DROP;
                    }
                    else
                    {
                        f = Messages.HA_HOST_SHUTDOWN_NTOL_DROP;
                    }

                    msg = string.Format(f, poolName, currentNtol, hostName, targetNtol);
                }

                using (var dlg = new ThreeButtonDialog(
                        new ThreeButtonDialog.Details(SystemIcons.Warning, msg, Messages.HIGH_AVAILABILITY),
                        ThreeButtonDialog.ButtonYes,
                        new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)
                        ))
                {
                    if (dlg.ShowDialog(Program.MainWindow) == DialogResult.No)
                    {
                        cancel = true;
                    }
                }

            });
            return cancel;
        }

    }
}