﻿/* 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. Diagnostics;
using System. IO;
using System. Linq;
using System. Text;
using System. Windows. Forms;
using XenAdmin. Controls;
using XenAdmin. Core;
using XenAPI;


namespace XenAdmin. Wizards. PatchingWizard
{
    public partial class PatchingWizard_ModePage : XenTabPage
    {
        private bool _tooltipShowing;

        public PatchingWizard_ModePage()
        {
            InitializeComponent();          
        }

        public override string Text
        {
            get { return Messages. PATCHINGWIZARD_MODEPAGE_TEXT; }
        }

        public override string PageTitle
        {
            get { return Messages. PATCHINGWIZARD_MODEPAGE_TITLE; }
        }

        public override string HelpID
        {
            get { return "UpdateMode"; }
        }

        public override bool EnablePrevious()
        {
            return true;
        }

        public override string NextText(bool isLastPage)
        {
            return Messages. UPDATES_WIZARD_APPLY_UPDATE;
        }

        protected override void PageLoadedCore(PageLoadedDirection direction)
        {
            var anyPoolForbidsAutostart = SelectedServers. Select(s => Helpers. GetPoolOfOne(s. Connection)).Any(p => p.IsAutoUpdateRestartsForbidden());

            // this will be true if a patch has restartHost guidance or if livepatching fails
            bool someHostMayRequireRestart;
            bool automaticDisabled;

            switch (SelectedUpdateType)
            {
                case UpdateType. Legacy:
                    ManualTextInstructions = ModePoolPatch(out someHostMayRequireRestart);
                    automaticDisabled = anyPoolForbidsAutostart && someHostMayRequireRestart;
                    break;
                case UpdateType. ISO:
                    ManualTextInstructions = PoolUpdate ! = null
                        ?  ModePoolUpdate(out someHostMayRequireRestart)
                        : ModeSuppPack(out someHostMayRequireRestart);
                    automaticDisabled = anyPoolForbidsAutostart && someHostMayRequireRestart;
                    break;
                default:
                    ManualTextInstructions = null;
                    automaticDisabled = true;
                    break;
            }

            if (ManualTextInstructions == null || ManualTextInstructions. Count == 0)
            {
                textBoxLog. Text = Messages. PATCHINGWIZARD_MODEPAGE_NOACTION;
            }
            else
            {
                var sb = new StringBuilder();
                foreach (var kvp in ManualTextInstructions)
                {
                    sb. AppendFormat("{0}:", kvp. Key).AppendLine();
                    sb. AppendIndented(kvp. Value).AppendLine();
                }
                textBoxLog. Text = sb. ToString();
            }

            AutomaticRadioButton. Enabled = ! automaticDisabled;
            AutomaticRadioButton. Checked = ! automaticDisabled;
            ManualRadioButton. Checked = automaticDisabled;

            if (automaticDisabled)
            {
                tableLayoutPanel1.MouseMove += tableLayoutPanel1_MouseMove;
            }

            if (SelectedUpdateType == UpdateType. ISO)
            {
                removeUpdateFileCheckBox. Checked = false;
                removeUpdateFileCheckBox. Visible = false;
            }
        }

        protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
        {
            tableLayoutPanel1.MouseMove -= tableLayoutPanel1_MouseMove;
        }

        /// <summary>
        /// Display the tooltip over the automatic radio button only if it is disabled
        /// </summary>
        private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
        {
            var control = tableLayoutPanel1.GetChildAtPoint(e. Location);
            if (control ! = null && ! control. Enabled && control is RadioButton)
            {
                if (_tooltipShowing) return;

                automaticRadioButtonTooltip. Show(Messages. POOL_FORBIDS_AUTOMATIC_RESTARTS,
                    AutomaticRadioButton,
                    e.Location. X, control. Height/2);
                _tooltipShowing = true;
            }
            else
            {
                automaticRadioButtonTooltip. Hide(AutomaticRadioButton);
                _tooltipShowing = false;
            }
        }

        public override bool EnableNext()
        {
            return AutomaticRadioButton. Checked || ManualRadioButton. Checked;
        }

        private void UpdateEnablement()
        {
            textBoxLog. Enabled = ManualRadioButton. Checked;
            OnPageUpdated();
        }

        #region Accessors

        public Dictionary<Pool, StringBuilder> ManualTextInstructions { get; private set; }

        public bool IsAutomaticMode
        {
            get { return AutomaticRadioButton. Checked; }
        }

        public bool RemoveUpdateFile
        {
            get { return removeUpdateFileCheckBox. Checked; }
        }

        public List<Pool> SelectedPools { private get; set; }
        public List<Host> SelectedServers { private get; set; }
        public Dictionary<string, livepatch_status> LivePatchCodesByHost { private get; set; }
        public Pool_update PoolUpdate { private get; set; }
        public Pool_patch Patch { private get; set; }
        public UpdateType SelectedUpdateType { private get; set; }

        #endregion

        private void AutomaticRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            UpdateEnablement();
        }

        private void ManualRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            UpdateEnablement();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string filePath = Path. GetTempFileName();

            using (var fileStream = File. Create(filePath))
            using (var sw = new StreamWriter(fileStream))
            {
                sw. Write(textBoxLog. Text);
                sw. Flush();
            }
            Process. Start("notepad.exe", filePath);
        }

        #region Guidance builder

        private Dictionary<Pool, StringBuilder> ModePoolPatch(out bool someHostMayRequireRestart)
        {
            someHostMayRequireRestart = false;

            if (Patch == null || Patch.after_apply_guidance == null || Patch.after_apply_guidance. Count == 0)
                return null;

            var applicableServers = SelectedServers. Where(h => Patch. AppliedOn(h) == DateTime. MaxValue).ToList();
            var serversPerPool = GroupServersPerPool(SelectedPools, applicableServers);

            var total = new Dictionary<Pool, StringBuilder>();

            foreach (var guide in Patch.after_apply_guidance)
            {
                var result = GetGuidanceList(guide, serversPerPool, null, out someHostMayRequireRestart);
                if (result == null)
                    continue;
                foreach (var kvp in result)
                {
                    if (total. ContainsKey(kvp. Key))
                        total[kvp. Key].Append(kvp. Value).AppendLine();
                    else
                        total[kvp. Key] = kvp. Value;
                }
            }

            return total;
        }

        private Dictionary<Pool, StringBuilder> ModePoolUpdate(out bool someHostMayRequireRestart)
        {
            someHostMayRequireRestart = false;

            if (PoolUpdate == null || PoolUpdate.after_apply_guidance == null || PoolUpdate.after_apply_guidance. Count == 0)
                return null;

            var applicableServers = SelectedServers. Where(h => ! PoolUpdate. AppliedOn(h)).ToList();
            var serversPerPool = GroupServersPerPool(SelectedPools, applicableServers);

            var total = new Dictionary<Pool, StringBuilder>();

            foreach (var guide in PoolUpdate.after_apply_guidance)
            {
                var result = GetGuidanceList(guide, serversPerPool, LivePatchCodesByHost, out someHostMayRequireRestart);
                if (result == null)
                    continue;
                foreach (var kvp in result)
                {
                    if (total. ContainsKey(kvp. Key))
                        total[kvp. Key].Append(kvp. Value).AppendLine();
                    else
                        total[kvp. Key] = kvp. Value;
                }
            }

            return total;
        }

        private Dictionary<Pool, StringBuilder> ModeSuppPack(out bool someHostMayRequireRestart)
        {
            var serversPerPool = GroupServersPerPool(SelectedPools, SelectedServers);
            return GetGuidanceList(after_apply_guidance.restartHost, serversPerPool, null, out someHostMayRequireRestart);
        }


        private Dictionary<Pool, List<Host>> GroupServersPerPool(List<Pool> pools, List<Host> servers)
        {
            servers. Sort();
            var dict = new Dictionary<Pool, List<Host>>();

            foreach (var pool in pools)
            {
                var hosts = pool. Connection. Cache. Hosts;
                var hostsPerPool = servers. Where(s => hosts. Contains(s)).ToList();
                if (hostsPerPool. Count > 0)
                    dict[pool] = hostsPerPool;
            }
            return dict;
        }


        private static Dictionary<Pool, StringBuilder> GetGuidanceList(update_after_apply_guidance guide, Dictionary<Pool, List<Host>> serversPerPool, Dictionary<string, livepatch_status> livePatchCodesByHost, out bool someHostMayRequireRestart)
        {
            someHostMayRequireRestart = false;

            switch (guide)
            {
                case update_after_apply_guidance.restartHost:
                    return GetGuidanceListRestartHost(serversPerPool, livePatchCodesByHost, out someHostMayRequireRestart);
                case update_after_apply_guidance.restartXAPI:
                    return GetGuidanceListRestartXapi(serversPerPool);
                case update_after_apply_guidance.restartPV:
                    return GetGuidanceListRestartVm(serversPerPool, vm => ! vm. IsHVM());
                case update_after_apply_guidance.restartHVM:
                    return GetGuidanceListRestartVm(serversPerPool, vm => vm. IsHVM());
                default:
                    return null;
            }
        }

        private static Dictionary<Pool, StringBuilder> GetGuidanceList(after_apply_guidance guide, Dictionary<Pool, List<Host>> serversPerPool, Dictionary<string, livepatch_status> livePatchCodesByHost, out bool someHostMayRequireRestart)
        {
            someHostMayRequireRestart = false;

            switch (guide)
            {
                case after_apply_guidance.restartHost:
                    return GetGuidanceListRestartHost(serversPerPool, livePatchCodesByHost, out someHostMayRequireRestart);
                case after_apply_guidance.restartXAPI:
                    return GetGuidanceListRestartXapi(serversPerPool);
                case after_apply_guidance.restartPV:
                    return GetGuidanceListRestartVm(serversPerPool, vm => ! vm. IsHVM());
                case after_apply_guidance.restartHVM:
                    return GetGuidanceListRestartVm(serversPerPool, vm => vm. IsHVM());
                default:
                    return null;
            }
        }


        private static Dictionary<Pool, StringBuilder> GetGuidanceListRestartHost(Dictionary<Pool, List<Host>> serversPerPool, Dictionary<string, livepatch_status> livePatchCodesByHost, out bool someHostMayRequireRestart)
        {
            someHostMayRequireRestart = false;
            var dict = new Dictionary<Pool, StringBuilder>();

            foreach (var kvp in serversPerPool)
            {
                var sb = new StringBuilder();
                foreach (var server in kvp. Value)
                {
                    if (livePatchCodesByHost ! = null && livePatchCodesByHost. ContainsKey(server.uuid)
                        && livePatchCodesByHost[server.uuid] == livepatch_status.ok_livepatch_complete)
                        continue;

                    var msg = server. IsMaster()
                        ?  string. Format("{0} ({1})", server. Name(), Messages. MASTER)
                        : server. Name();
                    sb. AppendIndented(msg).AppendLine();
                }

                if (sb. Length > 0)
                {
                    sb. Insert(0, Messages. PATCHINGWIZARD_MODEPAGE_RESTARTSERVERS + Environment. NewLine);
                    dict[kvp. Key] = sb;
                    someHostMayRequireRestart = true;
                }
            }
            return dict;
        }

        private static Dictionary<Pool, StringBuilder> GetGuidanceListRestartXapi(Dictionary<Pool, List<Host>> serversPerPool)
        {
            var dict = new Dictionary<Pool, StringBuilder>();

            foreach (var kvp in serversPerPool)
            {
                var sb = new StringBuilder();
                foreach (var server in kvp. Value)
                {
                    var msg = server. IsMaster()
                        ?  string. Format("{0} ({1})", server. Name(), Messages. MASTER)
                        : server. Name();
                    sb. AppendIndented(msg).AppendLine();
                }

                if (sb. Length > 0)
                {
                    sb. Insert(0, Messages. PATCHINGWIZARD_MODEPAGE_RESTARTXAPI + Environment. NewLine);
                    dict[kvp. Key] = sb;
                }
            }
            return dict;
        }

        private static Dictionary<Pool, StringBuilder> GetGuidanceListRestartVm(Dictionary<Pool, List<Host>> serversPerPool, Func<VM, bool> predicate)
        {
            var dict = new Dictionary<Pool, StringBuilder>();

            foreach (var kvp in serversPerPool)
            {
                var sb = new StringBuilder();
                foreach (var server in kvp. Value)
                {
                    foreach (var vmRef in server.resident_VMs)
                    {
                        var vm = server. Connection. Resolve(vmRef);
                        if (vm ! = null && vm.is_a_real_vm() && predicate. Invoke(vm))
                            sb. AppendIndented(vm. Name()).AppendLine();
                    }
                }

                if (sb. Length > 0)
                {
                    sb. Insert(0, Messages. PATCHINGWIZARD_MODEPAGE_RESTARTVMS + Environment. NewLine);
                    dict[kvp. Key] = sb;
                }
            }

            return dict;
        }

        #endregion
    }
}
