// RibbonUIElementManagerBase.cs
// Copyright (c) 2016 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using Haestad.Framework.Manager;
using Haestad.Framework.UI.Integration;
using Haestad.Support.Storage;
using Haestad.Support.Support;

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// Abstract base class for managing groups of logical RibbonUIElements that are able to be customized by a user.
    /// A user should not be able to REMOVE commands; only collapse them (make them invisible), re-order, re-label etc.
    /// </summary>
    public abstract class RibbonUIElementManagerBase : RibbonCustomizationManagerBase, IStorageElement
    {
        #region Constructor
        public RibbonUIElementManagerBase(int storageElementId, string managerName, int managerVersion, IRibbonUICommandFactory ribbonUICommandFactory)
            : base(storageElementId, 
                  managerName, 
                  managerVersion, 
                  StorageStrings.ElementLabel, 
                  StorageStrings.ElementCountLabel, 
                  INITIAL_CAPACITY)
        {
            RibbonUICommandFactory = ribbonUICommandFactory;
            AssociatedUIElementUpdated = false;
        }
        #endregion

        #region Public Override Methods
        public override object Add(object[] arguments)
        {
            int position = Add();
            IRibbonUIElement element = (IRibbonUIElement)ElementAt(position);
            if (arguments != null)
            {
                string associatedCommandName = (string)arguments[0];
                element.AssociatedCommandName = associatedCommandName;

                bool isVisible = (bool)arguments[1];
                element.IsVisible = isVisible;

                bool separatorAfter = (bool)arguments[2];
                element.SeparatorAfter = separatorAfter;
            }
            else
                throw new Exception("Something awry in RibbonUIElementManagerBase::Add()"); // I18N. OK.

            return element;
        }

        public override void PostRead()
        {
            SynchronizeWithDefaultElements();
            AfterSynchronize();
        }
        #endregion

        #region Protected Virtual Methods
        protected virtual void AfterSynchronize()
        {
            /*No-op*/
        }
        #endregion

        #region Protected Override Methods
        protected override void InitializeNewElement(int position)
        {
            // Note: <whar> Initialize state here that cannot be read from disk / user modified, 
            // but which is needed for the re-constituted object to be complete.
            RibbonUIElementBase element = ElementAt(position) as RibbonUIElementBase;
            element.RibbonUICommandFactory = RibbonUICommandFactory;
        }

        protected override void BeforeResetToDefaults()
        {
            // Note: <whar> We need to update the associated UIElement for each IRibbonUIElement in the defaults list because
            // that information was not available until the list was loaded into the Ribbon QAT.

            if (!AssociatedUIElementUpdated)
            {
                UpdateDefaultElementsAssociatedUIElements();
                AssociatedUIElementUpdated = true;
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Takes the list of elements (commands) loaded from disk and synchronizes with the default element list by:
        /// 1. Removing commands that no longer exist in the default list.
        /// 2. Adding commands that are in the default list, but missing from the loaded list.
        /// 3. Visibility of existing commands (ones the user may have already configured) remain as per the user configured settings.
        /// 4. Ensures the same order of commands as specified in the default list (managers that allow users to customize order need 
        /// to override AfterSynchronize and apply the user specified order).
        /// 5. Any settings that need to be forced from the default list (e.g., Separator after settings might only make sense in the default list) 
        /// need to override AfterSynchronize.
        /// </summary>
        private void SynchronizeWithDefaultElements()
        {
            IList<INamable> loadedElements = new List<INamable>(Elements().Count);
            foreach (INamable namableObject in Elements())
                loadedElements.Add(namableObject);

            SupportLibrary.SynchronizeNamedLists(DefaultElements, ref loadedElements);

            // Note: <whar> Now we have a list of loadedElements that may or may not include direct references to the DefaultElements
            // that needs to remain immutable (so we can reset to defaults if needed), so we take the synchronized list and load a copy
            // of it into the Elements() guy by cloning every item from the loadedElements list.

            ElementListManagerElementCollection collection = Elements();
            collection.Clear();

            foreach (RibbonUIElementBase ribbonUIElement in loadedElements)
                ribbonUIElement.AddClone();
        }

        /// <summary>
        /// Upon a reset operation, copies the AssociatedUIElement, from the first loaded IRibbonUIElement to the corresponding entry 
        /// in the DefaultElements list, since this property is not available on the loaded elements until the Ribbon is loaded. 
        /// So, to get around this order of operations issue, on the first reset we back fill that association to the default list.
        /// </summary>
        private void UpdateDefaultElementsAssociatedUIElements()
        {
            foreach (IRibbonUIElement loadedRibbonUIElement in Elements())
            {
                foreach (IRibbonUIElement defaultRibbonUIElement in DefaultElements)
                {
                    if (loadedRibbonUIElement.AssociatedCommandName == defaultRibbonUIElement.AssociatedCommandName)
                    {
                        defaultRibbonUIElement.AssociatedUIElement = loadedRibbonUIElement.AssociatedUIElement;
                        break;
                    }
                }
            }
        }
        #endregion

        #region Private Properties
        private IRibbonUICommandFactory RibbonUICommandFactory
        {
            get;
            set;
        }

        private bool AssociatedUIElementUpdated
        {
            get;
            set;
        }
        #endregion

        #region Private Structs
        private struct StorageStrings
        {
            public const string ElementLabel = "RibbonUIElement"; // DO NOT LOCALIZE <whar>.
            public const string ElementCountLabel = "CountOfRibbonUIElements"; // DO NOT LOCALIZE <whar>.
        }
        #endregion

        #region Private Constants
        private const int INITIAL_CAPACITY = 32;
        #endregion
    }
}
