// RibbonCustomizationManagerBase.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 ribbon settings list managers that use a standard Xml format for persisting settings.
    /// </summary>
    public abstract class RibbonCustomizationManagerBase : ElementListManagerBase, IObjectListManager
    {
        #region Constructor
        public RibbonCustomizationManagerBase(int storageElementId, string managerName, int managerVersion, string elementLabel, string elementCountLabel, int capacity)
            : base(capacity)
        {
            Id = storageElementId;
            ManagerName = managerName;
            ManagerVersion = managerVersion;
            ElementLabel = elementLabel;
            ElementCountLabel = elementCountLabel;
            InitialCapacity = capacity;
        }
        #endregion

        #region IObjectListManager Members
        IList IObjectListManager.Elements()
        {
            return Elements();
        }
        #endregion

        #region IDisconnectedElementManager Members
        public abstract Type ElementType { get; }

        public abstract object Add(object[] arguments);
        #endregion

        #region IStorageElement Members
        public int Id
        {
            get;
            private set;
        }

        public bool LazyRead
        {
            get { return false; }
        }

        public string Name
        {
            get { return ManagerName; }
        }

        public int Version
        {
            get { return ManagerVersion; }
        }

        public int Commit()
        {
            return 0;
        }

        public virtual void PostRead()
        {
            /*No-op*/
        }
        #endregion

        #region Public Virtual Methods
        public virtual void Write(IStorageWriteStream stream)
        {
            IStorageXmlWriteStream writeStream = stream as IStorageXmlWriteStream;

            writeStream.WriteStartElement(ManagerName);
            writeStream.WriteAttribute(ElementCountLabel, Count);
            foreach (IStorageAtom storageAtom in Elements())
            {
                writeStream.WriteStartElement(ElementLabel);
                storageAtom.Write(stream);
                writeStream.WriteEndElement();
            }

            writeStream.WriteEndElement();
        }

        public virtual void Read(IStorageReadStream stream, int version)
        {
            Elements().Clear();

            try
            {
                IStorageXmlReadStream readStream = GetStorageXmlReadStream(stream);

                int countOfElements = GetNumberOfElementsToRead(readStream);
                for (int i = 0; i < countOfElements; i++)
                {
                    Add();
                    CheckNextContentNode(readStream);
                    GetNextStorageAtom(i).Read(stream, version);
                }
            }
            catch
            {
                // Note: <whar> Something crashed and burned with the settinsg file read. Best we can do here is revert to loaded defaults.
                DoResetToDefaults();
            }
        }
        #endregion

        #region Public Methods
        public void ResetToDefaults()
        {
            BeforeResetToDefaults();
            DoResetToDefaults();
        }

        /// <summary>
        /// Default elements are expected (normally) to exist by the time this method is called (in TelerikWPFRibbonBuilder::LoadRibbonSettings). 
        /// This is the standard protocol. Special cases that do not have explicit defaults (like DropDownToggleSettingsManager) need to jump through
        /// extra hoops to build up the defaults (careful not to blow away loaded settings) in the TelerikWPFRibbonBuilder::AfterRibbonLayoutComplete() method.
        /// This method could be internal, except it's used in test cases in Haestad.Drawing.UI.Integration.
        /// </summary>
        public void CacheDefaultElements()
        {
            DefaultElements.Clear();
            foreach (INamable namableObject in Elements())
                DefaultElements.Add(namableObject);
        }
        #endregion

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

        #region Protected Methods
        protected IStorageXmlReadStream GetStorageXmlReadStream(IStorageReadStream stream)
        {
            IStorageXmlReadStream readStream = stream as IStorageXmlReadStream;
            if (readStream == null)
                throw new StorageException("Expected IStorageXmlReadStream in RibbonCustomizationManagerBase::Read()"); // I18N. OK.
            return readStream;
        }
        #endregion

        #region Private Methods
        private void DoResetToDefaults()
        {
            Elements().Clear();
            foreach (ICloneableElement clonableElement in DefaultElements)
                clonableElement.AddClone();
        }

        private IStorageAtom GetNextStorageAtom(int index)
        {
            IStorageAtom storageAtom = ElementAt(index) as IStorageAtom;
            if (storageAtom == null)
                throw new StorageException("Element must implement IStorageAtom in RibbonCustomizationManagerBase::Read()"); // I18N. OK.
            return storageAtom;
        }

        private int GetNumberOfElementsToRead(IStorageXmlReadStream readStream)
        {
            const int NEED_TO_READ_COUNT = -1;

            readStream.NextContentNode();
            int countOfElements = NEED_TO_READ_COUNT;
            if (readStream.Name != ManagerName)
            {
                readStream.NextContentNode();
                if (readStream.Name != ManagerName)
                    countOfElements = 0;
                else
                    countOfElements = NEED_TO_READ_COUNT;
            }

            if (countOfElements == NEED_TO_READ_COUNT)
                countOfElements = readStream.ReadIntAttribute(ElementCountLabel);

            return countOfElements;
        }

        private void CheckNextContentNode(IStorageXmlReadStream readStream)
        {
            readStream.NextContentNode();
            if (readStream.Name != ElementLabel)
                throw new StorageException("Expected node " + ElementLabel + " in RibbonCustomizationManagerBase::CheckNextContentNode()"); // I18N. OK.
        }
        #endregion

        #region Protected Properties
        protected string ManagerName
        {
            get;
            set;
        }

        protected int ManagerVersion
        {
            get;
            set;
        }

        #region Protected Properties
        /// <summary>
        /// An immutable cache of the default elements for this manager.
        /// This collection should not be altered during the liftime of the manager, except to create the initial default list.
        /// Furthermore, the elements in DefaultElements should never be *added* to Elements(); only *cloned* into Elements().
        /// </summary>
        protected IList<INamable> DefaultElements
        {
            get
            {
                if (m_defaultElements == null)
                    m_defaultElements = new List<INamable>(InitialCapacity);
                return m_defaultElements;
            }
        }
        #endregion
        #endregion

        #region Private Properties
        private string ElementLabel
        {
            get;
            set;
        }

        private string ElementCountLabel
        {
            get;
            set;
        }

        private int InitialCapacity
        {
            get;
            set;
        }
        #endregion

        #region Private Fields
        private IList<INamable> m_defaultElements;
        #endregion
    }
}
