// RibbonSearchManager.cs
// Copyright (c) 2017 Bentley Systems, Incorporated. All Rights Reserved.

using Haestad.Support.Support;
using Haestad.Support.Library;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Telerik.Windows.Controls;

[assembly: InternalsVisibleTo("Haestad.Framework.UI.Integration.Test, PublicKey=" +
    "002400000480000094000000060200000024000052534131000400000100010025b2f82c499245" +
    "27a7853779cf30993d30cf63f0dea189775ddb4d071be50d54e5f910b20cab403f08c5343a38a4" +
    "1ddfb8452cace2996428f4fbf666e622dc3d0f982bc347fe94309e5448d6d22675d8655abee826" +
    "27104632ecf481097c91571411bb1d224f8fe9227638dfcf844e47f78fce62332c705b3c0fea0e" +
    "d4bd46bd"
    )]

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// A class that manages ribbon search functionality.
    /// </summary>
    internal class RibbonSearchManager
    {
        #region Constructor
        internal RibbonSearchManager(RadComboBox searchCombo, RadRibbonView ribbon, IImageProvider imageProvider, IQueryRibbonTabIndex queryRibbonTabIndex)
        {
            SearchCombo = searchCombo;
            Ribbon = ribbon;
            ImageProvider = imageProvider;
            QueryRibbonTabIndex = queryRibbonTabIndex;

            InitializeSearchCombo();
            InitializeEvents();
        }
        #endregion

        #region Event Handlers

        #region SearchCombo
        private void SearchCombo_Loaded(object sender, RoutedEventArgs e)
        {
            RadComboBox comboBox = sender as RadComboBox;
            if (comboBox == null)
                return;

            SearchComboTextBox = comboBox.GetChildOfType<TextBox>();
            if (SearchComboTextBox != null)
            {
                InitializeSearchComboTextBoxEvents();
                InstallCustomButtonIntoRadComboBox();
            }
        }

        private void SearchCombo_DropDownClosed(object sender, EventArgs e)
        {
            if (SearchCombo.SelectedItem != null)
            {
                SearchComboItem searchComboItem = SearchCombo.SelectedItem as SearchComboItem;

                if (searchComboItem.SearchResultItem != null) // Note: <whar> Can be "No matches guy".
                    searchComboItem.SearchResultItem.AssociatedCommand.Execute(null);
            }

            ClearSearchCombo();
        }
        #endregion

        #region SearchComboTextBox
        private void SearchComboTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            HandleSearchComboTextBoxTextChanged();
        }

        private void SearchComboTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter && SearchCombo.IsDropDownOpen && SearchCombo.SelectedItem == null)
            {
                SearchCombo.SelectedItem = SearchComboItemPool[0];
                SearchCombo.IsDropDownOpen = false;
            }
            else if (e.Key == Key.Escape)
            {
                ClearSearchCombo();
            }
        }

        private void SearchCombo_LostFocus(object sender, RoutedEventArgs e)
        {
            ClearSearchCombo();
        }
        #endregion

        #region ClearSelectionButton
        private delegate void AfterCloseDropDownDelegate();

        private void ClearSelectionButton_Click(object sender, RoutedEventArgs e)
        {
            if (StringLibrary.IsBlank(SearchCombo.Text))
                SearchComboTextBox.Focus();
            else if (SearchCombo.IsDropDownOpen)
            {
                SearchCombo.IsDropDownOpen = false;

                // Note: <whar> For some reason cannot just call SearchComboTextBox.Focus(); here. 
                // Need to use this delegate, to save the user an additional click.
                Ribbon.Dispatcher.BeginInvoke(new AfterCloseDropDownDelegate(AfterCloseDropDown), System.Windows.Threading.DispatcherPriority.Input, null);
            }
            else
            {
                SearchComboTextBox.Text = String.Empty;
                UpdateClearSelectionButtonImage();
                SearchComboTextBox.Focus();
            }
        }
        #endregion

        #region SearchComboDropDownButton
        private void SearchComboDropDownButton_Click(object sender, RoutedEventArgs e)
        {
            // Note: <whar> This logic is opposite to what you'd expect, since Telerik's internal handlers, presumbly, get first bite
            // and have already opened the drop down as a response to the same user click.
            // So when the drop down is open we want to make sure it shows the right stuff.
            if (SearchCombo.IsDropDownOpen)
            {
                HandleSearchComboTextBoxTextChanged();
            }
        }
        #endregion

        #endregion

        #region Internal Methods
        /// <summary>
        /// Adds a reference to a ribbonUICommand for the purposes of search functionality.
        /// </summary>
        /// <param name="ribbonUICommand">The ribbbon command to reference in search.</param>
        /// <param name="element">The FrameworkElement associated with the ribbonUICommand.</param>
        internal void AddReferenceToCommand(IRibbonUICommand ribbonUICommand, FrameworkElement element)
        {
            AddReferenceToCommand(ribbonUICommand, element, String.Empty);
        }

        /// <summary>
        /// Adds a reference to a ribbonUICommand for the purposes of search functionality.
        /// </summary>
        /// <param name="ribbonUICommand">The ribbbon command to reference in search.</param>
        /// <param name="element">The FrameworkElement associated with the ribbonUICommand.</param>
        /// <param name="alternateSearchTerm">An alternate search term that can be used to search for the command (the localized command label is always used).</param>
        internal void AddReferenceToCommand(IRibbonUICommand ribbonUICommand, FrameworkElement element, string alternateSearchTerm)
        {
            if (!(ribbonUICommand is IRibbonSearchLinkedUICommand))
            {
                TraceLibrary.WriteLine(HmTraceLevel.Release, "TelerikWPFRibbonBuilder::AddReferenceToCommand(). ribbonUICommand with Name: " + ribbonUICommand.Name +
                    " does not implement IRibbonSearchLinkedUICommand and therefore cannot be searched."); // I18N. OK.
                return;
            }

            IRibbonSearchLinkedUICommand linkedUICommand = (IRibbonSearchLinkedUICommand)ribbonUICommand;
            if (linkedUICommand.HiddenFromSearch)
                return;

            AddCommandReference(ribbonUICommand, element);

            string localizedLabel = ribbonUICommand.GetCleanLabel().ToLowerInvariant();
            RegisterLocalizedLabelForSearch(localizedLabel, ribbonUICommand);

            if (alternateSearchTerm != String.Empty)
                RegisterLocalizedLabelForSearch(alternateSearchTerm.ToLowerInvariant(), ribbonUICommand);
        }

        internal void RemoveReferenceToCommand(IRibbonUICommand ribbonUICommand, FrameworkElement element)
        {
            RemoveCommandReference(ribbonUICommand, element);

            if (!CommandReferences.ContainsKey(ribbonUICommand))
            {
                IList<string> localizedLabels = RibbonUICommandToLocalizedLabelList[ribbonUICommand];

                foreach (string localizedLabel in localizedLabels)
                {
                    if (LocalizedLabelToRibbonUICommandList.ContainsKey(localizedLabel))
                    {
                        IList<IRibbonUICommand> commands = LocalizedLabelToRibbonUICommandList[localizedLabel];
                        if (commands.Contains(ribbonUICommand))
                            commands.Remove(ribbonUICommand);
                    }
                }

                RibbonUICommandToLocalizedLabelList.Remove(ribbonUICommand);
            }
        }
        #endregion

        #region Internal Properties
        internal bool SearchComboIsActive
        {
            get { return SearchCombo.IsKeyboardFocusWithin; }
        }
        #endregion

        #region Private Methods
        private void AddCommandReference(IRibbonUICommand ribbonUICommand, FrameworkElement element)
        {
            if (CommandReferences.ContainsKey(ribbonUICommand))
            {
                IList<FrameworkElement> elements = CommandReferences[ribbonUICommand];
                if (!elements.Contains(element))
                    elements.Add(element);
            }
            else
            {
                const int DEFAULT_SIZE = 4; // Note: <whar> One command may be associated with a handful of FrameworkElements.
                IList<FrameworkElement> elements = new List<FrameworkElement>(DEFAULT_SIZE);
                elements.Add(element);
                CommandReferences.Add(ribbonUICommand, elements);
            }
        }

        private void RemoveCommandReference(IRibbonUICommand ribbonUICommand, FrameworkElement element)
        {
            if (CommandReferences.ContainsKey(ribbonUICommand))
            {
                IList<FrameworkElement> elements = CommandReferences[ribbonUICommand];
                if (elements.Contains(element))
                    elements.Remove(element);

                if (elements.Count == 0)
                    CommandReferences.Remove(ribbonUICommand);
            }
        }

        private void RegisterLocalizedLabelForSearch(string localizedLabel, IRibbonUICommand ribbonUICommand)
        {
            if (LocalizedLabelToRibbonUICommandList.ContainsKey(localizedLabel))
            {
                IList<IRibbonUICommand> commands = LocalizedLabelToRibbonUICommandList[localizedLabel];
                if (!commands.Contains(ribbonUICommand))
                    commands.Add(ribbonUICommand);
            }
            else
            {
                const int DEFAULT_SIZE = 4; // Note: <whar> We won't often have more than one command with the same localized label; usually for domain elements.
                IList<IRibbonUICommand> commands = new List<IRibbonUICommand>(DEFAULT_SIZE);
                commands.Add(ribbonUICommand);
                LocalizedLabelToRibbonUICommandList.Add(localizedLabel, commands);
            }

            if (RibbonUICommandToLocalizedLabelList.ContainsKey(ribbonUICommand))
            {
                IList<string> localizedLabels = RibbonUICommandToLocalizedLabelList[ribbonUICommand];
                if (!localizedLabels.Contains(localizedLabel))
                    localizedLabels.Add(localizedLabel);
            }
            else
            {
                const int DEFAULT_SIZE = 2; // Note: <whar> We won't often have more than one localized label with the same command (special cases that have alternate labels, basically).
                IList<string> localizedLabels = new List<string>(DEFAULT_SIZE);
                localizedLabels.Add(localizedLabel);
                RibbonUICommandToLocalizedLabelList.Add(ribbonUICommand, localizedLabels);
            }
        }

        private void InitializeSearchCombo()
        {
            SearchCombo.CanAutocompleteSelectItems = false;
            SearchCombo.IsEditable = true;
            SearchCombo.IsFilteringEnabled = false;
            SearchCombo.IsTextSearchEnabled = false;
            SearchCombo.StaysOpenOnEdit = true;
            SearchCombo.UpdateTextOnLostFocus = true; // Note: <whar> Seems to have no effect, but no harm in being true.

            SearchCombo.EmptyText = TextManager.Current["hfui_SearchRibbonWithHotKey"];

            SearchCombo.DisplayMemberPath = "SearchComboItemDisplayMember"; // I18N. OK.
        }

        private void InitializeEvents()
        {
            SearchCombo.Loaded += SearchCombo_Loaded;
            SearchCombo.DropDownClosed += SearchCombo_DropDownClosed;
            SearchCombo.LostFocus += SearchCombo_LostFocus;
        }

        private void ClearSearchCombo()
        {
            SearchCombo.Text = String.Empty;
            SearchCombo.Items.Clear();
            SearchCombo.SelectedItem = null;

            // Note: <whar> Work around (aka HACK) to get the RadComboBox to revert to the EmptyText value.
            // NET6MIGRATION
            //Ribbon.HelpButton.Focus();
            Ribbon.Focus();
        }

        private void UpdateClearSelectionButtonImage()
        {
            if (SearchCombo.Text != null && SearchCombo.Text.Length > 0)
                ClearSelectionButton.SmallImage = ImageProvider.GetImage(false, "exclude");
            else
                ClearSelectionButton.SmallImage = ImageProvider.GetImage(false, "search");
        }

        private void PopulateSearchCombo()
        {
            IList<RibbonSearchResultItem> searchResults = GetSearchResults(SearchCombo.Text);

            SearchCombo.Items.Clear();

            if (searchResults.Count == 0)
            {
                SearchComboItemPool[0].MakeTextOnly(TextManager.Current["hfui_NoSearchMatches"]);
                SearchCombo.Items.Add(SearchComboItemPool[0]);
            }
            else
            {
                int searchResultItemCount = 0;
                foreach (RibbonSearchResultItem searchResultItem in searchResults)
                {
                    SearchComboItemPool[searchResultItemCount].SearchResultItem = searchResultItem;
                    SearchCombo.Items.Add(SearchComboItemPool[searchResultItemCount]);
                    searchResultItemCount++;

                    if (searchResultItemCount == MAXIMUM_DISPLAYED_SEARCH_ENTRIES)
                    {
                        int numberOfRemainingEntries = searchResults.Count - searchResultItemCount;
                        if (numberOfRemainingEntries > 0)
                        {
                            SearchComboItemPool[searchResultItemCount].MakeTextOnly(TextManager.Current["hfui_MoreMatches", numberOfRemainingEntries.ToString(CultureInfo.InvariantCulture)]);
                            SearchCombo.Items.Add(SearchComboItemPool[searchResultItemCount]);
                        }
                        break;
                    }
                }
            }
        }

        private IList<RibbonSearchResultItem> GetSearchResults(string searchComboText)
        {
            IList<MatchingSearchResult> matchingSearchResults = GetMatchingSearchResults(searchComboText);

            const int PRE_SIZE_FACTOR = 3;
            IList<RibbonSearchResultItem> searchResults = new List<RibbonSearchResultItem>(matchingSearchResults.Count * PRE_SIZE_FACTOR);

            foreach (MatchingSearchResult matchingSearchResult in matchingSearchResults)
            {
                IRibbonSearchLinkedUICommand linkedUICommand = matchingSearchResult.RibbonUICommand as IRibbonSearchLinkedUICommand;
                if (linkedUICommand == null)
                    continue;

                // Note: <whar> If the commands have been setup correctly, this will not happen, as IRibbonBackstageUICommand guys should only
                // be associated with backstage tabs (which do not directly participate in search).
                if (matchingSearchResult.RibbonUICommand is IRibbonBackstageUICommand)
                {
                    TraceLibrary.WriteLine(HmTraceLevel.Release, "RibbonSearchManager::GetSearchResults(). Excluded command name: " 
                        + matchingSearchResult.RibbonUICommand.Name + " from search results; since it implements IRibbonBackstageUICommand."); // I18N. OK.
                    continue;
                }

                IList<RibbonLocation> elementLocations = null;
                IList<FrameworkElement> elementRibbonTabs = null;
                IList<string> elementPaths = null;

                linkedUICommand.GetLinkedElementSearchData(RibbonUICommandLinkedElementTypes.All, out elementLocations, out elementRibbonTabs, out elementPaths);

                for (int i = 0; i < elementLocations.Count; ++i)
                {
                    searchResults.Add(new RibbonSearchResultItem(elementLocations[i], matchingSearchResult.RelativeRank,
                        QueryRibbonTabIndex.GetIndexForTab((RadRibbonTab)elementRibbonTabs[i]), elementPaths[i], matchingSearchResult.RibbonUICommand));
                }
            }

            searchResults.Sort((item1, item2) => RibbonSearchHelper.RibbonSearchResultItemComparison(item1, item2));

            return searchResults;
        }

        /// <summary>
        /// Returns a list of search results subject to a basic matching and ranking algorithm.
        /// </summary>
        /// <param name="searchString">The user entered search string.</param>
        /// <returns>The list of search results, ranked, but un-sorted.</returns>
        private IList<MatchingSearchResult> GetMatchingSearchResults(string searchString)
        {
            const int DEFAULT_RESULT_SET_SIZE = 256; // Note: <whar> Initial search strings will have a high number of matches.

            IList<MatchingSearchResult> matchingSearchResults = new List<MatchingSearchResult>(DEFAULT_RESULT_SET_SIZE);
            if (StringLibrary.IsBlank(searchString))
                return matchingSearchResults;

            // Note: <whar> matchingCommandNames is keyed by the command name, to make it easier to see the final list of matchingCommandNames in the debugger.
            IDictionary<string, MatchingSearchResult> matchingCommandNames = new Dictionary<string, MatchingSearchResult>(DEFAULT_RESULT_SET_SIZE);
            IDictionary<int, IRibbonUICommand> commandRanks = new Dictionary<int, IRibbonUICommand>(DEFAULT_RESULT_SET_SIZE);

            string searchStringLower = searchString.ToLowerInvariant();
            int searchStringLength = searchStringLower.Length;

            foreach (string localizedLabel in LocalizedLabelToRibbonUICommandList.Keys)
            {
                if (searchStringLength == localizedLabel.Length)
                {
                    if (localizedLabel.Contains(searchStringLower)) // Note: <whar> Testing shows having this as a separate if is faster.
                    {
                        AddMatchingCommand(matchingCommandNames, commandRanks, localizedLabel,
                            GetMatchRank(SearchMatchRank.ExactMatch, 0, 0, localizedLabel));
                        continue;
                    }
                }

                bool foundMatch = false;
                IList<string> labelWords = StringLibrary.GetWords(localizedLabel);
                for (int i = 0; i < labelWords.Count; ++i)
                {
                    if (labelWords[i].StartsWith(searchStringLower))
                    {
                        AddMatchingCommand(matchingCommandNames, commandRanks, localizedLabel,
                            GetMatchRank(SearchMatchRank.StartsWith, i, 0, localizedLabel));
                        foundMatch = true;
                        break;
                    }
                }

                if (foundMatch)
                    continue;

                if (localizedLabel.StartsWith(searchStringLower))
                {
                    AddMatchingCommand(matchingCommandNames, commandRanks, localizedLabel,
                        GetMatchRank(SearchMatchRank.StartsWith, 0, 0, localizedLabel));
                    continue;
                }
                
                const int NOT_FOUND = -1;
                int matchIndex = 0;

                foundMatch = false;
                for (int i = 0; i < labelWords.Count; ++i)
                {
                    matchIndex = labelWords[i].IndexOf(searchStringLower);
                    if (matchIndex != NOT_FOUND)
                    {
                        AddMatchingCommand(matchingCommandNames, commandRanks, localizedLabel,
                            GetMatchRank(SearchMatchRank.Contains, i, matchIndex, localizedLabel));
                        foundMatch = true;
                        break;
                    }
                }

                if (foundMatch)
                    continue;

                matchIndex = localizedLabel.IndexOf(searchStringLower);
                if (matchIndex != NOT_FOUND)
                {
                    AddMatchingCommand(matchingCommandNames, commandRanks, localizedLabel,
                        GetMatchRank(SearchMatchRank.Contains, 0, matchIndex, localizedLabel));
                    continue;
                }
            }

            foreach (MatchingSearchResult matchingSearchResult in matchingCommandNames.Values)
                matchingSearchResults.Add(matchingSearchResult);

            return matchingSearchResults;
        }

        /// <summary>
        /// Returns an integer rank that is designed to rank based on quantitative search result criteria and avoid clashes through keeping
        /// scoring of various features at different orders of magnitude from most important to least important.
        /// A LOWER rank number is BETTER.
        /// </summary>
        /// <param name="searchMatchRank">The type of match; gets the most weight.</param>
        /// <param name="wordIndex">The word index, first (0), second (1) etc in the localized label that the match is made. The higher the index, the worse the match rank.</param>
        /// <param name="matchIndex">The match index (which part of the word the matching term is found; i.e., result of string.IndexOf). The higher the index, the worse the match rank.</param>
        /// <param name="localizedLabel">The localized label being matched. Used to tie break searches that have equivalent matches; shorter labels win since a higher % of the label is matched.
        ///  e.g., Search term "pum" for Labels "pump" and "pumps" would score the same searchMatchRank, same wordIndex, same matchIndex, but the localized length is higher for "pumps" so it would
        ///  have a lower rank (higher number) than "pump".</param>
        /// <returns>The match rank number.</returns>
        private int GetMatchRank(SearchMatchRank searchMatchRank, int wordIndex, int matchIndex, string localizedLabel)
        {
            const int WORD_FACTOR = 100000;
            const int MATCH_INDEX_FACTOR = 1000;
            const int LENGTH_FACTOR = 10;

            //     10e8                                10e6                         10e4                                           10e2
            return (int)searchMatchRank + (wordIndex * WORD_FACTOR) + (matchIndex * MATCH_INDEX_FACTOR) + (localizedLabel.Length * LENGTH_FACTOR);
        }

        private void AddMatchingCommand(IDictionary<string, MatchingSearchResult> matchingCommandNames, 
            IDictionary<int, IRibbonUICommand> commandRanks, string localizedLabel, int relativeRank)
        {
            int commandRank = relativeRank;

            IList<IRibbonUICommand> commands = LocalizedLabelToRibbonUICommandList[localizedLabel];
            foreach (IRibbonUICommand command in commands)
            {
                commandRank++;

                MatchingSearchResult matchingSearchResult = null;
                if (!matchingCommandNames.ContainsKey(command.Name))
                {
                    while (commandRanks.ContainsKey(commandRank))
                        commandRank++;

                    matchingSearchResult = new MatchingSearchResult(command, commandRank);
                    commandRanks.Add(commandRank, command);
                    matchingCommandNames.Add(command.Name, matchingSearchResult);
                }
                else
                {
                    while (commandRanks.ContainsKey(commandRank))
                        commandRank++;

                    matchingSearchResult = matchingCommandNames[command.Name];
                    if (matchingSearchResult.RelativeRank > commandRank)
                    {
                        matchingSearchResult = new MatchingSearchResult(command, commandRank);
                        commandRanks.Add(commandRank, command);
                        matchingCommandNames[command.Name] = matchingSearchResult;
                    }
                }
            }
        }

        private void InitializeSearchComboItemPool()
        {
            m_searchComboItemPool = new List<SearchComboItem>(MAXIMUM_DISPLAYED_SEARCH_ENTRIES + 1); // Note: <whar> + 1 for "x more items" guy.
            for (int i = 0; i < MAXIMUM_DISPLAYED_SEARCH_ENTRIES + 1; ++i)
                m_searchComboItemPool.Add(new SearchComboItem(ImageProvider, SearchCombo));
        }

        private void AfterCloseDropDown()
        {
            SearchComboTextBox.Focus();
            SearchComboTextBox.Text = String.Empty;
        }

        private void InitializeSearchComboTextBoxEvents()
        {
            SearchComboTextBox.TextChanged += SearchComboTextBox_TextChanged;
            SearchComboTextBox.KeyDown += SearchComboTextBox_KeyDown;
        }

        private void InstallCustomButtonIntoRadComboBox()
        {
            Grid comboGrid = SearchComboTextBox.Parent as Grid;
            if (comboGrid != null)
            {
                SearchComboDropDownButton = comboGrid.GetChildOfType<RadToggleButton>();

                if (SearchComboDropDownButton != null)
                {
                    ColumnDefinition autoColumn = new ColumnDefinition();
                    autoColumn.Width = GridLength.Auto;
                    comboGrid.ColumnDefinitions.Add(autoColumn);

                    comboGrid.Children.Add(ClearSelectionButton);
                    Grid.SetColumn(ClearSelectionButton, 1);
                    Grid.SetColumn(SearchComboDropDownButton, 2);

                    ClearSelectionButton.Click += ClearSelectionButton_Click;
                    SearchComboDropDownButton.Click += SearchComboDropDownButton_Click;
                }
            }
        }

        private void HandleSearchComboTextBoxTextChanged()
        {
            UpdateClearSelectionButtonImage();

            if (SearchCombo.Text == null || SearchCombo.Text == String.Empty)
            {
                SearchCombo.Items.Clear();
                return;
            }
            else
                PopulateSearchCombo();

            if (!SearchCombo.IsDropDownOpen)
                SearchCombo.IsDropDownOpen = true;

            SearchComboTextBox.SelectionLength = 0;
            SearchComboTextBox.SelectionStart = SearchComboTextBox.Text.Length;
        }
        #endregion

        #region Internal Properties
        internal RadComboBox SearchCombo
        {
            get;
            private set;
        }
        #endregion

        #region Private Properties
        /// <summary>
        /// A register of localized (searchable) labels to the IRibbonUICommands that are associated with the label.
        /// </summary>
        private IDictionary<string, IList<IRibbonUICommand>> LocalizedLabelToRibbonUICommandList
        {
            get
            {
                if (m_localizedLabelToRibbonUICommandList == null)
                    m_localizedLabelToRibbonUICommandList = new Dictionary<string, IList<IRibbonUICommand>>(DEFAULT_DICTIONARY_SIZE);
                return m_localizedLabelToRibbonUICommandList;
            }
        }

        /// <summary>
        /// A register of IRibbonUICommands to the localized labels (searchable) that are registered against those commands.
        /// Needed in order to efficiently un-register a command (allows to get to the localized labels that are referencing the command).
        /// </summary>
        private IDictionary<IRibbonUICommand, IList<string>> RibbonUICommandToLocalizedLabelList
        {
            get
            {
                if (m_ribbonUICommandToLocalizedLabelList == null)
                    m_ribbonUICommandToLocalizedLabelList = new Dictionary<IRibbonUICommand, IList<string>>(DEFAULT_DICTIONARY_SIZE);
                return m_ribbonUICommandToLocalizedLabelList;
            }
        }

        private IDictionary<IRibbonUICommand, IList<FrameworkElement>> CommandReferences
        {
            get
            {
                if (m_commandReferences == null)
                    m_commandReferences = new Dictionary<IRibbonUICommand, IList<FrameworkElement>>(DEFAULT_DICTIONARY_SIZE);
                return m_commandReferences;
            }
        }

        /// <summary>
        /// A pool of re-usable combo items for adding to the search combo.
        /// I'm not sure how important this is in WPF, but logically, one of these guys has a TextBlock, Image etc so it must hold
        /// on to system resource handles at some level, therefore, re-using the same pool of guys seems like a better practice than
        /// creating them over and over every time a user types into search.
        /// </summary>
        private IList<SearchComboItem> SearchComboItemPool
        {
            get
            {
                if (m_searchComboItemPool == null)
                    InitializeSearchComboItemPool();
                return m_searchComboItemPool;
            }
        }

        private RadRibbonButton ClearSelectionButton
        {
            get
            {
                if (m_clearSelectionButton == null)
                {
                    m_clearSelectionButton = new RadRibbonButton();
                    m_clearSelectionButton.Name = "__clearSelection__"; // I18N. OK.
                    m_clearSelectionButton.SmallImage = ImageProvider.GetImage(false, "search");
                    m_clearSelectionButton.Focusable = false;
                }
                return m_clearSelectionButton;
            }
        }

        private TextBox SearchComboTextBox
        {
            get;
            set;
        }

        private RadToggleButton SearchComboDropDownButton
        {
            get;
            set;
        }

        private RadRibbonView Ribbon
        {
            get;
            set;
        }

        private IImageProvider ImageProvider
        {
            get;
            set;
        }

        private IQueryRibbonTabIndex QueryRibbonTabIndex
        {
            get;
            set;
        }
        #endregion

        #region Private Fields
        private IDictionary<string, IList<IRibbonUICommand>> m_localizedLabelToRibbonUICommandList;
        private IDictionary<IRibbonUICommand, IList<string>> m_ribbonUICommandToLocalizedLabelList;
        private IDictionary<IRibbonUICommand, IList<FrameworkElement>> m_commandReferences;
        private IList<SearchComboItem> m_searchComboItemPool;
        private RadRibbonButton m_clearSelectionButton;
        #endregion

        #region Private Constants
        private const int DEFAULT_DICTIONARY_SIZE = 32;
        private const int MAXIMUM_DISPLAYED_SEARCH_ENTRIES = 15;
        #endregion
    }
}
