/* 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.Linq;
using System.Windows.Forms;
using XenAdmin.Actions;
using XenAdmin.Controls;
using XenAdmin.Core;
using XenAdmin.Dialogs;
using XenAdmin.Dialogs.WarningDialogs;
using XenAdmin.Network;
using XenAPI;
using System.Drawing;


namespace XenAdmin.Wizards.NewSRWizard_Pages.Frontends
{
    public partial class LVMoHBA : XenTabPage
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
               
        private List<FibreChannelDevice> _selectedDevices = new List<FibreChannelDevice>();

        public LVMoHBA()
        {
            InitializeComponent();
            SrType = SR.SRTypes.lvmohba;
        }

        public virtual SR.SRTypes SrType { get; set; }

        public virtual bool ShowNicColumn { get { return false; } }

        public virtual LvmOhbaSrDescriptor CreateSrDescriptor(FibreChannelDevice device)
        {
            return SrType == SR.SRTypes.gfs2 ? new Gfs2HbaSrDescriptor(device) : new LvmOhbaSrDescriptor(device, Connection);
        }

        public virtual LvmOhbaSrDescriptor CreateLvmSrDescriptor(FibreChannelDevice device)
        {
            return new LvmOhbaSrDescriptor(device, Connection);
        }
        
        #region XenTabPage overrides

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

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

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

        protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
        {
            if (direction == PageLoadedDirection.Back)
                return;

            Host master = Helpers.GetMaster(Connection);
            if (master == null)
            {
                cancel = true;
                return;
            }
            
            SrDescriptors = new List<LvmOhbaSrDescriptor>();

            var existingSrDescriptors = new List<LvmOhbaSrDescriptor>();
            var formatDiskDescriptors = new List<LvmOhbaSrDescriptor>();

            foreach (var device in _selectedDevices)
            {
                LvmOhbaSrDescriptor descr = CreateSrDescriptor(device);

                // create an LVM descriptor, to be used with sr.probe
                LvmOhbaSrDescriptor lvmdescr = CreateLvmSrDescriptor(device);
                SR.SRTypes srType = lvmdescr is FcoeSrDescriptor ? SR.SRTypes.lvmofcoe : SR.SRTypes.lvmohba; //srType is a workaround instead of SrType

                var action = new SrProbeAction(Connection, master, srType, lvmdescr.DeviceConfig);// TODO: use SRType and descr
                using (var dlg = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
                    dlg.ShowDialog(this);

                if (!action.Succeeded)
                {
                    cancel = true;
                    return;
                }

                descr.UUID = SrWizardHelpers.ExtractUUID(action.Result);

                if (!string.IsNullOrEmpty(SrWizardType.UUID))
                {
                    // Check LUN contains correct SR
                    if (descr.UUID == SrWizardType.UUID)
                    {
                        SrDescriptors.Add(descr);
                        continue;
                    }

                    using (var dlog = new ThreeButtonDialog(
                        new ThreeButtonDialog.Details(SystemIcons.Error,
                            String.Format(Messages.INCORRECT_LUN_FOR_SR, SrWizardType.SrName), Messages.XENCENTER)))
                    {
                        dlog.ShowDialog(this);
                    }

                    cancel = true;
                    return;
                }
                
                if (string.IsNullOrEmpty(descr.UUID))
                {
                    // No existing SRs were found on this LUN. If allowed to create
                    // a new SR, ask the user if they want to proceed and format.
                    if (!SrWizardType.AllowToCreateNewSr)
                    {
                        using (var dlog = new ThreeButtonDialog(
                            new ThreeButtonDialog.Details(SystemIcons.Error,
                                Messages.NEWSR_LUN_HAS_NO_SRS, Messages.XENCENTER)))
                        {
                            dlog.ShowDialog(this);
                        }

                        cancel = true;
                        return;
                    }

                    if (!Program.RunInAutomatedTestMode)
                        formatDiskDescriptors.Add(descr);
                }
                else
                {
                    // CA-17230: Check this isn't a detached SR. If it is then just continue
                    SR sr = SrWizardHelpers.SrInUse(descr.UUID);
                    if (sr != null)
                    {
                        SrDescriptors.Add(descr);
                        continue;
                    }

                    // We found a SR on this LUN. Will ask user for choice later.
                    existingSrDescriptors.Add(descr);
                }
            }

            if (!cancel && existingSrDescriptors.Count > 0)
            {
                var launcher = new LVMoHBAWarningDialogLauncher(this, existingSrDescriptors, true);
                launcher.ShowWarnings();
                cancel = launcher.Cancelled;
                if (!cancel && launcher.SrDescriptors.Count > 0)
                    SrDescriptors.AddRange(launcher.SrDescriptors);
            }

            if (!cancel && formatDiskDescriptors.Count > 0)
            {
                var launcher = new LVMoHBAWarningDialogLauncher(this, formatDiskDescriptors, false);
                launcher.ShowWarnings();
                cancel = launcher.Cancelled;
                if (!cancel && launcher.SrDescriptors.Count > 0)
                    SrDescriptors.AddRange(launcher.SrDescriptors);
            }
        }

        public override bool EnableNext()
        {
            UpdateSelectionButtons();
            return _selectedDevices.Count > 0;
        }

        public override bool EnablePrevious()
        {
            if (SrWizardType.DisasterRecoveryTask && SrWizardType.SrToReattach == null)
                return false;

            return true;
        }

        public override void PopulatePage()
        {
            colNic.Visible = ShowNicColumn; 
            dataGridView.Rows.Clear();

            var vendorGroups = from device in FCDevices
                         group device by device.Vendor into g
                         orderby g.Key
                         select new { VendorName = g.Key, Devices = g.OrderBy(x => x.Serial) };

            foreach (var vGroup in vendorGroups)
            {
                var vendorRow = new VendorRow(vGroup.VendorName);
                dataGridView.Rows.Add(vendorRow);

                using (var font = new Font(dataGridView.DefaultCellStyle.Font, FontStyle.Bold))
                    vendorRow.DefaultCellStyle = new DataGridViewCellStyle(dataGridView.DefaultCellStyle)
                        {
                            Font = font,
                            SelectionBackColor = dataGridView.DefaultCellStyle.BackColor,
                            SelectionForeColor = dataGridView.DefaultCellStyle.ForeColor
                        };

                var deviceRows = from device in vGroup.Devices select new FCDeviceRow(device, ShowNicColumn);
                dataGridView.Rows.AddRange(deviceRows.ToArray());
            }
        }

        public override string NextText(bool isLastPage)
        {
            // for Dundee or greater connections, we have "Storage provisioning settings" page after this page, so the Next button should say "Next", not "Create"
            return Helpers.DundeeOrGreater(Connection) ?  Messages.WIZARD_BUTTON_NEXT : Messages.NEWSR_LVMOHBA_NEXT_TEXT;
        }

        public override void SelectDefaultControl()
        {
            dataGridView.Select();
        }

        #endregion

        #region Event handlers

        private void dataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (_srWizardType.SrToReattach != null)
                return;

            if (e.ColumnIndex != colCheck.Index || e.RowIndex < 0 || e.RowIndex > dataGridView.RowCount - 1)
                return;

            var deviceRow = dataGridView.Rows[e.RowIndex] as FCDeviceRow;
            if (deviceRow == null)
                return;

            deviceRow.Cells[colCheck.Index].Value = !(bool)deviceRow.Cells[colCheck.Index].Value;

            UpdateSelectedDevices();
            OnPageUpdated();
        }

        private void dataGridView_SelectionChanged(object sender, EventArgs e)
        {
            if (_srWizardType.SrToReattach == null)
                return;

            UpdateSelectedDevices();
            OnPageUpdated();
        }

        private void buttonSelectAll_Click(object sender, EventArgs e)
        {
            foreach (var row in dataGridView.Rows)
            {
                var deviceRow = row as FCDeviceRow;
                if (deviceRow != null && deviceRow.Cells.Count > 0)
                    deviceRow.Cells[colCheck.Index].Value = true;
            }

            UpdateSelectedDevices();
            OnPageUpdated();
        }

        private void buttonClearAll_Click(object sender, EventArgs e)
        {
            foreach (var row in dataGridView.Rows)
            {
                var deviceRow = row as FCDeviceRow;
                if (deviceRow != null && deviceRow.Cells.Count > 0)
                    deviceRow.Cells[colCheck.Index].Value = false;
            }

            UpdateSelectedDevices();
            OnPageUpdated();
        }

        #endregion

        private void UpdateSelectedDevices()
        {
            if (SrWizardType.SrToReattach == null)
            {
                //when creating a new SR the checkbox column is visible
                _selectedDevices = (from DataGridViewRow row in dataGridView.Rows
                                    let deviceRow = row as FCDeviceRow
                                    where deviceRow != null
                                          && deviceRow.Cells.Count > 0
                                          && (bool)(deviceRow.Cells[colCheck.Index].Value)
                                    select deviceRow.Device).ToList();
            }
            else
            {
                //when reattaching SR the checkbox column is hidden
                _selectedDevices = (from DataGridViewRow row in dataGridView.Rows
                                    let deviceRow = row as FCDeviceRow
                                    where deviceRow != null && deviceRow.Selected
                                    select deviceRow.Device).ToList();
            }
        }

        private void UpdateSelectionButtons()
        {
            if (buttonSelectAll.Visible)
                buttonSelectAll.Enabled = _selectedDevices.Count < FCDevices.Count;

            if (buttonClearAll.Visible)
                buttonClearAll.Enabled = _selectedDevices.Count > 0;
        }

        public bool FiberChannelScan(IWin32Window owner, IXenConnection connection, out List<FibreChannelDevice> devices)
        {
            devices = new List<FibreChannelDevice>();

            Host master = Helpers.GetMaster(connection);
            if (master == null)
                return false;

            SR.SRTypes srType = this is LVMoFCoE ? SR.SRTypes.lvmofcoe : SR.SRTypes.lvmohba; //srType is a workaround instead of SrType

            FibreChannelProbeAction action = new FibreChannelProbeAction(master, srType);// TODO: use SRType
            using (var  dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
                dialog.ShowDialog(owner); //Will block until dialog closes, action completed

            if (!action.Succeeded)
                return false;

            try
            {
                devices = FibreChannelProbeParsing.ProcessXML(action.Result);

                if (devices.Count == 0)
                {
                    using (var dlg = new ThreeButtonDialog(
                        new ThreeButtonDialog.Details(SystemIcons.Warning, Messages.FIBRECHANNEL_NO_RESULTS, Messages.XENCENTER)))
                    {
                        dlg.ShowDialog();
                    }

                    return false;
                }
                return true;
            }
            catch (Exception e)
            {
                log.Debug("Exception parsing result of fibre channel scan", e);
                log.Debug(e, e);
                using (var dlg = new ThreeButtonDialog(
                    new ThreeButtonDialog.Details(SystemIcons.Warning, Messages.FIBRECHANNEL_XML_ERROR, Messages.XENCENTER)))
                {
                    dlg.ShowDialog();
                }

                return false;
            }
        }
        #region Accessors

        public List<FibreChannelDevice> FCDevices { private get; set; }

        private SrWizardType _srWizardType;
        public SrWizardType SrWizardType
        {
            private get
            {
                return _srWizardType;
            }
            set
            {
                _srWizardType = value;

                bool creatingNew = _srWizardType.SrToReattach == null;
                
                colCheck.Visible = creatingNew;
                dataGridView.MultiSelect = creatingNew;
                buttonSelectAll.Visible = creatingNew;
                buttonClearAll.Visible = creatingNew;
                labelCreate.Visible = creatingNew;
                labelReattach.Visible = !creatingNew;
            }
        }

        public List<LvmOhbaSrDescriptor> SrDescriptors { get; private set; }

        /// <summary>
        /// min size
        /// </summary>
        public long SRSize
        {
            get
            {
                long size = long.MaxValue;
                foreach (var srDescriptor in SrDescriptors)
                {
                    if (srDescriptor.Device.Size < size)
                        size = srDescriptor.Device.Size;
                }
                return size;
            }
        }

        #endregion
        
        #region Nested classes

        private class FCDeviceRow : DataGridViewRow
        {
            public FibreChannelDevice Device { get; private set; }

            public FCDeviceRow(FibreChannelDevice device, bool showNicColumn)
            {
                Device = device;

                string id = string.IsNullOrEmpty(device.SCSIid) ? device.Path : device.SCSIid;
                string details = String.Format("{0}:{1}:{2}:{3}", device.adapter, device.channel, device.id, device.lun);

                Cells.AddRange(new DataGridViewCheckBoxCell{ThreeState = false, Value = false},
                    new DataGridViewTextBoxCell { Value = Util.DiskSizeString(device.Size) },
                    new DataGridViewTextBoxCell { Value = device.Serial },
                    new DataGridViewTextBoxCell { Value = id },
                    new DataGridViewTextBoxCell { Value = details });

                if (showNicColumn)
                    Cells.Add(new DataGridViewTextBoxCell {Value = device.eth});
            }
        }

        private class VendorRow : DataGridViewRow
        {
            public VendorRow(string vendor)
            {
                Cells.AddRange(new DataGridViewCheckBoxCellVendor(),
                    new DataGridViewTextBoxCell { Value = vendor },
                    new DataGridViewTextBoxCell(),
                    new DataGridViewTextBoxCell(),
                    new DataGridViewTextBoxCell());
            }

            private class DataGridViewCheckBoxCellVendor : DataGridViewCheckBoxCell
            {
                protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
                {
                    using (var normalBrush = new SolidBrush(OwningRow.DefaultCellStyle.BackColor))
                    using (var selectedBrush = new SolidBrush(OwningRow.DefaultCellStyle.SelectionBackColor))
                    {
                        graphics.FillRectangle(
                            (elementState & DataGridViewElementStates.Selected) != 0 ? selectedBrush : normalBrush,
                            cellBounds.X, cellBounds.Y, cellBounds.Width, cellBounds.Height);
                    }
                }
            }
        }

        public enum UserSelectedOption { Cancel, Reattach, Format, Skip }

        private class LVMoHBAWarningDialogLauncher
        {
            private readonly List<LvmOhbaSrDescriptor> inputSrDescriptors;
            private readonly bool foundExistingSRs;
            private readonly IWin32Window owner;
            
            public List<LvmOhbaSrDescriptor> SrDescriptors { get; private set; }
            public bool Cancelled { get; private set; }

            public LVMoHBAWarningDialogLauncher(IWin32Window owner, List<LvmOhbaSrDescriptor> srDescriptors,
                bool foundExistingSRs)
            {
                this.owner = owner;
                this.foundExistingSRs = foundExistingSRs;
                inputSrDescriptors = srDescriptors;
                SrDescriptors = new List<LvmOhbaSrDescriptor>();
            }

            private UserSelectedOption GetSelectedOption(LvmOhbaSrDescriptor lvmOhbaSrDescriptor,
                out bool repeatForRemainingLUNs)
            {
                int remainingCount = inputSrDescriptors.Count - 1 - inputSrDescriptors.IndexOf(lvmOhbaSrDescriptor);

                using (var dialog = new LVMoHBAWarningDialog(lvmOhbaSrDescriptor.Device, remainingCount, foundExistingSRs))
                {
                    dialog.ShowDialog(owner);
                    repeatForRemainingLUNs = dialog.RepeatForRemainingLUNs;
                    return dialog.SelectedOption;
                }
            }

            public void ShowWarnings()
            {
                bool repeatForRemainingLUNs = false;
                UserSelectedOption selectedOption = UserSelectedOption.Cancel;

                foreach (LvmOhbaSrDescriptor lvmOhbaSrDescriptor in inputSrDescriptors)
                {
                    if (!repeatForRemainingLUNs)
                    {
                        selectedOption = GetSelectedOption(lvmOhbaSrDescriptor, out repeatForRemainingLUNs);
                    }

                    switch (selectedOption)
                    {
                        case UserSelectedOption.Format:
                            lvmOhbaSrDescriptor.UUID = null;
                            SrDescriptors.Add(lvmOhbaSrDescriptor);
                            break;
                        case UserSelectedOption.Reattach:
                            SrDescriptors.Add(lvmOhbaSrDescriptor);
                            break;
                        case UserSelectedOption.Cancel:
                            SrDescriptors.Clear();
                            Cancelled = true;
                            return;
                    }
                }
            }
        }

        #endregion
    }
}