//------------------------------------------------------------------------------
// <copyright file="ToolStripSettings.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

namespace System.Windows.Forms {
    using System;
    using System.Configuration;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis; 
    using System.Drawing;
    using System.Text;
    using System.Text.RegularExpressions;

    /// <include file='doc\ToolStripSettings.uex' path='docs/doc[@for="ToolStripSettings"]/*' />
    /// <devdoc> 
    ///     A settings class used by the ToolStripManager to save toolstrip settings.
    /// </devdoc>
    internal class ToolStripSettings : ApplicationSettingsBase {

        internal ToolStripSettings(string settingsKey) : base(settingsKey) {}
        
        [UserScopedSetting]
        [DefaultSettingValue("true")]
        public bool IsDefault {
            get {
                return (bool) this["IsDefault"]; 
            }
            set {
                this["IsDefault"] = value;
            }
        }
        
        [UserScopedSetting]
        public string ItemOrder {
            get {
                return this["ItemOrder"] as string;
            }
            set {
                this["ItemOrder"] = value;
            }
        }
        
        [UserScopedSetting]
        public string Name {
            get {
                return this["Name"] as string;
            }
            set {
                this["Name"] = value;
            }
        }

        [UserScopedSetting]
        [DefaultSettingValue("0,0")]
        public Point Location {
            get {
                return (Point) this["Location"];
            }
            set {
                this["Location"] = value;
            }
        }

        [UserScopedSetting]
        [DefaultSettingValue("0,0")]
        public Size Size {
            get {
                return (Size) this["Size"];
            }
            set {
                this["Size"] = value;
            }
        }

        [UserScopedSetting]
        public string ToolStripPanelName {
            get {
                return this["ToolStripPanelName"] as string;
            }
            set {
                this["ToolStripPanelName"] = value;
            }
        }
        
        [UserScopedSetting]
        [DefaultSettingValue("true")]
        public bool Visible {
            get {
                return (bool) this["Visible"];
            }
            set {
                this["Visible"] = value;
            }
        }
        
        
        public override void Save() {
            this.IsDefault = false;
            base.Save();
        }
    }

    /// <include file='doc\ToolStripSettings.uex' path='docs/doc[@for="ToolStripSettings"]/*' />
    /// <devdoc> 
    ///     Helper class used by ToolStripManager that implements most of the logic to save out and apply
    ///     settings for toolstrips on a form.
    /// </devdoc>
    internal class ToolStripSettingsManager {
        private Form form;
        private string formKey;
        
        internal ToolStripSettingsManager(Form owner, string formKey) {
            this.form = owner;
            this.formKey = formKey;
        }

        internal void Load() {
            ArrayList savedToolStripSettingsObjects = new ArrayList();

            foreach (ToolStrip toolStrip in FindToolStrips(true, form.Controls)) {
                if (toolStrip != null && !string.IsNullOrEmpty(toolStrip.Name)) {
                    ToolStripSettings toolStripSettings = new ToolStripSettings(GetSettingsKey(toolStrip));
                    
                    // Check if we have settings saved out for this toolstrip. If so, add it to our apply list.
                    if (!toolStripSettings.IsDefault) {
                        savedToolStripSettingsObjects.Add(new SettingsStub(toolStripSettings));
                    }
                }
            }

            ApplySettings(savedToolStripSettingsObjects);
        }
        
        internal void Save() {
            foreach (ToolStrip toolStrip in FindToolStrips(true, form.Controls)) {
                if (toolStrip != null && !string.IsNullOrEmpty(toolStrip.Name)) {
                    ToolStripSettings toolStripSettings = new ToolStripSettings(GetSettingsKey(toolStrip));
                    SettingsStub stub = new SettingsStub(toolStrip);

                    toolStripSettings.ItemOrder = stub.ItemOrder;
                    toolStripSettings.Name = stub.Name;
                    toolStripSettings.Location = stub.Location;
                    toolStripSettings.Size = stub.Size;
                    toolStripSettings.ToolStripPanelName = stub.ToolStripPanelName;
                    toolStripSettings.Visible = stub.Visible;
                    
                    toolStripSettings.Save();
                }
            }
        }

        internal static string GetItemOrder(ToolStrip toolStrip) {
            StringBuilder itemNames = new StringBuilder(toolStrip.Items.Count);

            for (int i = 0; i < toolStrip.Items.Count; i++) {
                itemNames.Append((toolStrip.Items[i].Name == null) ? "null" : toolStrip.Items[i].Name);
                if (i != toolStrip.Items.Count - 1) {
                    itemNames.Append(",");
                }
            }
            return itemNames.ToString();
        }

        private void ApplySettings(ArrayList toolStripSettingsToApply) {
            if (toolStripSettingsToApply.Count == 0) {
                return;
            }

            SuspendAllLayout(form);
        
        
            // iterate through all the toolstrips and build up a hash of where the items
            // are right now.
            Dictionary<string, ToolStrip> itemLocationHash = BuildItemOriginationHash();
           
            // build up a hash of where we want the ToolStrips to go
            Dictionary<object, List<SettingsStub>> toolStripPanelDestinationHash = new Dictionary<object, List<SettingsStub>>();
            
            foreach (SettingsStub toolStripSettings in toolStripSettingsToApply) {
                object destinationPanel = !string.IsNullOrEmpty(toolStripSettings.ToolStripPanelName) ? toolStripSettings.ToolStripPanelName : null;

                if (destinationPanel == null) {
                    // Not in a panel.
                    if (!string.IsNullOrEmpty(toolStripSettings.Name)) {
                        // apply the toolstrip settings.
                        ToolStrip toolStrip = ToolStripManager.FindToolStrip(form, toolStripSettings.Name);
                        ApplyToolStripSettings(toolStrip, toolStripSettings, itemLocationHash);
                    }
                }
                else {
                    // This toolStrip is in a ToolStripPanel. We will process it below.
                    if (!toolStripPanelDestinationHash.ContainsKey(destinationPanel)) {
                        toolStripPanelDestinationHash[destinationPanel] = new List<SettingsStub>();
                    }
            
                    toolStripPanelDestinationHash[destinationPanel].Add(toolStripSettings);
                }
            }
            
            // build up a list of the toolstrippanels to party on
            ArrayList toolStripPanels = FindToolStripPanels(true, form.Controls);
        
            foreach (ToolStripPanel toolStripPanel in toolStripPanels) {
                // set all the controls to visible false/
                foreach (Control c in toolStripPanel.Controls) {
                    c.Visible = false;
                }
                
                string toolStripPanelName = toolStripPanel.Name;

                // Handle the ToolStripPanels inside a ToolStripContainer
                if (String.IsNullOrEmpty(toolStripPanelName) && toolStripPanel.Parent is ToolStripContainer && !String.IsNullOrEmpty(toolStripPanel.Parent.Name)) {
                    toolStripPanelName = toolStripPanel.Parent.Name + "." + toolStripPanel.Dock.ToString();
                }
                toolStripPanel.BeginInit();
                // get the associated toolstrips for this panel
                if (toolStripPanelDestinationHash.ContainsKey(toolStripPanelName)) {
                    List<SettingsStub> stubSettings = toolStripPanelDestinationHash[toolStripPanelName];
        
                    if (stubSettings != null) {
                        foreach (SettingsStub settings in stubSettings) {
                            if (!string.IsNullOrEmpty(settings.Name)) {
                                // apply the toolstrip settings.
                                ToolStrip toolStrip = ToolStripManager.FindToolStrip(form, settings.Name);
                                ApplyToolStripSettings(toolStrip, settings, itemLocationHash);
                                toolStripPanel.Join(toolStrip, settings.Location);
                            }
                        }
                    }
                }
                toolStripPanel.EndInit();
            }

            ResumeAllLayout(form, true);
        }

        private void ApplyToolStripSettings(ToolStrip toolStrip, SettingsStub settings, Dictionary<string, ToolStrip> itemLocationHash) {
            if (toolStrip != null) {
                toolStrip.Visible = settings.Visible;
                toolStrip.Size = settings.Size;
                
                // Apply the item order changes.
                string itemNames = settings.ItemOrder;
                if (!string.IsNullOrEmpty(itemNames)) {
                    string[] keys = itemNames.Split(',');
                    Regex r = new Regex("(\\S+)");
            
                    // Shuffle items according to string.
                    for (int i = 0; ((i < toolStrip.Items.Count) && (i < keys.Length)); i++) {
                        Match match = r.Match(keys[i]);
                        if (match != null && match.Success) {
                            string key = match.Value;
                            if (!string.IsNullOrEmpty(key) && itemLocationHash.ContainsKey(key)) {
                                toolStrip.Items.Insert(i, itemLocationHash[key].Items[key]);
                            }
                        }
                    }
                }
            }
        }

        private Dictionary<string, ToolStrip> BuildItemOriginationHash() {
           ArrayList toolStrips = FindToolStrips(true, form.Controls);
           Dictionary<string, ToolStrip> itemLocationHash = new Dictionary<string, ToolStrip>();
        
           if (toolStrips != null) {
               foreach (ToolStrip toolStrip in toolStrips) {
                   foreach (ToolStripItem item in toolStrip.Items) {
                       if (!string.IsNullOrEmpty(item.Name)) {
                           Debug.Assert(!itemLocationHash.ContainsKey(item.Name), "WARNING: ToolStripItem name not unique.");
                           itemLocationHash[item.Name] = toolStrip;
                       }
                   }
               }
           }

           return itemLocationHash;
        }

        [
            SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")
        ]
        private ArrayList FindControls(Type baseType, bool searchAllChildren, Control.ControlCollection controlsToLookIn, ArrayList foundControls) {
            if ((controlsToLookIn == null) || (foundControls == null)) {
                return null;
            }
        
            try {
                // Perform breadth first search - as it's likely people will want controls belonging
                // to the same parent close to each other.
        
                for (int i = 0; i < controlsToLookIn.Count; i++) {
                    if (controlsToLookIn[i] == null) {
                        continue;
                    }
        
                    if (baseType.IsAssignableFrom(controlsToLookIn[i].GetType())) {
                        foundControls.Add(controlsToLookIn[i]);
                    }
                }
        
                // Optional recurive search for controls in child collections.
        
                if (searchAllChildren) {
                    for (int i = 0; i < controlsToLookIn.Count; i++) {
                        if (controlsToLookIn[i] == null || controlsToLookIn[i] is Form) {
                            continue;
                        }
                        if ((controlsToLookIn[i].Controls != null) && controlsToLookIn[i].Controls.Count > 0) {
                            // if it has a valid child collecion, append those results to our collection
                            foundControls = FindControls(baseType, searchAllChildren, controlsToLookIn[i].Controls, foundControls);
                        }
                    }
                }
            }
            catch (Exception e) {
                if (ClientUtils.IsCriticalException(e)) {
                    throw;
                }
            }
            return foundControls;
        }

        private ArrayList FindToolStripPanels(bool searchAllChildren, Control.ControlCollection controlsToLookIn) {
            return FindControls(typeof(ToolStripPanel), true, form.Controls, new ArrayList());
        }
        
        private ArrayList FindToolStrips(bool searchAllChildren, Control.ControlCollection controlsToLookIn) {
            return FindControls(typeof(ToolStrip), true, form.Controls, new ArrayList());
        }
        
        private string GetSettingsKey(ToolStrip toolStrip) {
            if (toolStrip != null) {
                return formKey + "." + toolStrip.Name;
            }

            return String.Empty;
        }

        private void ResumeAllLayout(Control start, bool performLayout) {
            Control.ControlCollection controlsCollection = start.Controls;
        
            for (int i = 0; i < controlsCollection.Count; i++) {
                ResumeAllLayout(controlsCollection[i], performLayout);
            }
        
            start.ResumeLayout(performLayout);
        }
        
        private void SuspendAllLayout(Control start) {
            start.SuspendLayout();
        
            Control.ControlCollection controlsCollection = start.Controls;
            for (int i = 0; i < controlsCollection.Count; i++) {
                SuspendAllLayout(controlsCollection[i]);
            }
        }

        /// <devdoc> 
        ///     Light weight structure that captures the properties we want to save as settings.
        /// </devdoc>
        private struct SettingsStub {
            public bool     Visible;
            public string   ToolStripPanelName;
            public Point    Location;
            public Size     Size;
            public string   ItemOrder;
            public string   Name;

            public SettingsStub(ToolStrip toolStrip) {
                this.ToolStripPanelName = String.Empty;
                ToolStripPanel parentPanel = toolStrip.Parent as ToolStripPanel;

                if (parentPanel != null) {
                    if (!String.IsNullOrEmpty(parentPanel.Name)) {
                        this.ToolStripPanelName = parentPanel.Name;
                    }
                    else if (parentPanel.Parent is ToolStripContainer && !String.IsNullOrEmpty(parentPanel.Parent.Name)) {
                        // Handle the case when the ToolStripPanel belongs to a ToolStripContainer.
                        this.ToolStripPanelName = parentPanel.Parent.Name + "." + parentPanel.Dock.ToString();
                    }

                    Debug.Assert(!String.IsNullOrEmpty(this.ToolStripPanelName), "ToolStrip was parented to a panel, but we couldn't figure out its name.");
                }

                this.Visible = toolStrip.Visible;
                this.Size = toolStrip.Size;
                this.Location = toolStrip.Location;
                this.Name = toolStrip.Name;
                this.ItemOrder = GetItemOrder(toolStrip);
               
            }
            public SettingsStub(ToolStripSettings toolStripSettings) {
                this.ToolStripPanelName = toolStripSettings.ToolStripPanelName;
                this.Visible = toolStripSettings.Visible;
                this.Size = toolStripSettings.Size;
                this.Location = toolStripSettings.Location;
                this.Name = toolStripSettings.Name;
                this.ItemOrder = toolStripSettings.ItemOrder;
            }
        }
    }
}
