using System;
using System.Collections.Generic;
using System.Text;
using _ElementState = nft.core.structure.CatalogVariableCollection.ElementState;

namespace nft.core.structure {
    public delegate void CatalogVarCollectionEventHandler(CatalogVarCollectionEventArgs arg);

    /// <summary>
    /// 設置する構造物の選択肢などを保持し、ツールフォームのモデルとするためのコレクション。
    /// ICatalogVariableと付随情報をリスト化して保持する。インデックスと名前検索併用可能
    /// </summary>
    public class CatalogVariableCollection {
        public enum ElementState { Added, Removed, Replaced, ModelUpdated };
        protected IList<string> order;
        protected ValueMap valMap;
        protected IDictionary<String, CatalogVarCollectionElement> map;
        protected IDictionary<String, object> current_cache;
        protected bool susupendModelEvent = false;
        protected List<KeyValuePair<string, ElementState>> modifications;
        protected bool listDirty = false;
        public event CatalogVarCollectionEventHandler ListElementsChanged;
        public event CatalogVarCollectionEventHandler VariableCurrentChanged;
        public event CatalogVarCollectionEventHandler VariableOptionChanged;

        public CatalogVariableCollection() {
            this.order = new List<string>();
            this.valMap = new ValueMap(this);
            this.map = new Dictionary<string, CatalogVarCollectionElement>();
            this.current_cache = new Dictionary<string, object>();
            this.modifications = new List<KeyValuePair<string, ElementState>>();
        }

        public CatalogVarCollectionElement this[string key] {
            get { return map[key]; }            
        }
        public CatalogVarCollectionElement this[int index] {
            get {
                return this[order[index]];
            }
        }

        public void SuspendModelEvent(){
            susupendModelEvent = true;
        }
        public void ResumeModelEvent() {
            susupendModelEvent = false;
            FireEventIfDirty();
        }

        protected void FireEventIfDirty() {
            if (modifications.Count > 0) {
                if (ListElementsChanged != null) {
                    ListElementsChanged(CreateModelEvent());
                }
                modifications.Clear();
            }
        }

        protected void SetElementModified(string key, ElementState state) {
            modifications.Add(new KeyValuePair<string, ElementState>(key, state));
            if (!susupendModelEvent) {
                FireEventIfDirty();
            }
        }

        protected CatalogVarModelEventArgs CreateModelEvent() {
            return new CatalogVarModelEventArgs(this, new List<KeyValuePair<string, ElementState>>(modifications));
        }

        public int AddElement(CatalogVarCollectionElement elm){
            string key = elm.Name;
            if (map.ContainsKey(key)) {
                //throw new InvalidOperationException("The Element of the name '"+key+"' is already exist.");
                return ReplaceElement(elm);
            } else {
                order.Add(key);
                SetElementInternal(elm);
                return order.Count-1;
            }
        }

        public int InsertElementAt(CatalogVarCollectionElement elm, int index) {
            string key = elm.Name;
            if (map.ContainsKey(key)) {
                //throw new InvalidOperationException("The Element of the name '" + key + "' is already exist.");
                return ReplaceElement(elm, index);
            } else {
                order.Insert(index, key);
                SetElementInternal(elm);
                return index;
            }
        }

        public int ReplaceElement(CatalogVarCollectionElement elm, int newIndex = -1) {
            string key = elm.Name;
            int idx = order.IndexOf(key);
            if (idx>=0) {
                if (newIndex >= 0 && idx != newIndex) {
                    order.RemoveAt(idx);
                    order.Insert(newIndex, key);
                }
                UnsetProxyHandlers(map[key]);
                SetElementInternal(elm, ElementState.Replaced);
                return newIndex;
            } else {
                if (newIndex >= 0) {
                    return InsertElementAt(elm, newIndex);
                } else {
                    return AddElement(elm);
                }
            }
        }

        public int RemoveElement(CatalogVarCollectionElement elm) {
            string key = elm.Name;
            int idx = order.IndexOf(key);
            if (idx >= 0) {
                order.RemoveAt(idx);
            }
            UnsetProxyHandlers(elm);
            map.Remove(key);
            SetElementModified(key, ElementState.Removed);
            return idx;
        }

        public int RemoveElementAt(int index) {
            CatalogVarCollectionElement elm = this[index];
            return RemoveElement(elm);
        }

        public ValueMap Values {
            get { return valMap; }
        }

        protected virtual void SetElementInternal(CatalogVarCollectionElement elm, ElementState es = ElementState.Added) {
            string key = elm.Name;
            map[key] = elm;
            TryRestoreCurrentCache(elm);
            SetProxyHandlers(elm);
            SetElementModified(key, es);
        }

        /// <summary>
        /// 直前に選択されていた値のヒントを渡す
        /// </summary>
        /// <param name="elm"></param>
        protected void TryRestoreCurrentCache(CatalogVarCollectionElement elm) {
            string key = elm.Name;
            if (current_cache.ContainsKey(key)) {
                elm.Variable.CurrentHint = current_cache[key];
            }
        }

        #region internal event handler management
        protected void SetProxyHandlers(CatalogVarCollectionElement elm) {
            elm.VariableCurrentChanged += VariableCurrentChangedProxy;
            elm.VariableOptionChanged += VariableOptionChangedProxy;
            elm.VariableModelChanged += VariableModelChangedProxy;
        }

        protected void UnsetProxyHandlers(CatalogVarCollectionElement elm) {
            if (elm != null) {
                elm.VariableCurrentChanged -= VariableCurrentChangedProxy;
                elm.VariableOptionChanged -= VariableOptionChangedProxy;
                elm.VariableModelChanged -= VariableModelChangedProxy;
            }
        }

        protected void VariableModelChangedProxy(CatalogVarCollectionElementEventArgs arg) {
            TryRestoreCurrentCache(arg.Source);
            SetElementModified(arg.Source.Name, ElementState.ModelUpdated);
        }

        protected void VariableOptionChangedProxy(CatalogVarCollectionElementEventArgs arg) {
            if (VariableOptionChanged != null) {
                VariableOptionChanged(new CatalogVarViewEventArgs(this, arg.Source, arg.OriginalEvent));
            }
        }

        protected void VariableCurrentChangedProxy(CatalogVarCollectionElementEventArgs arg) {
            if (VariableCurrentChanged != null) {
                string key = arg.Source.Name;
                /// ビュー側から変更された場合に限り、現在の値のヒントを名前をキーにして保存
                current_cache.Add(arg.Source.Name, arg.OriginalEvent.Source.CurrentHint);
                VariableCurrentChanged(new CatalogVarViewEventArgs(this, arg.Source, arg.OriginalEvent));
            }
        }
        #endregion

        public class ValueMap : IEnumerable<KeyValuePair<string, object>> {
            CatalogVariableCollection owner;

            internal ValueMap(CatalogVariableCollection collection) {
                this.owner = collection;
            }

            public object this[string key] {
                get {
                    CatalogVarCollectionElement elm = owner.map[key];
                    return elm.Variable.Current;
                }
            }
            public object this[int index] {
                get {
                    return this[owner.order[index]];
                }
            }

            public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
                foreach (string key in owner.order) {
                    CatalogVarCollectionElement elm;
                    if (owner.map.TryGetValue(key, out elm) && elm.Variable != null) {
                        KeyValuePair<string, object> pair = new KeyValuePair<string, object>(key, elm.Variable.Current);
                        yield return pair;
                    }
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return GetEnumerator();
            }
        }
    }

    public class CatalogVarCollectionEventArgs : EventArgs {
        public readonly CatalogVariableCollection Source;
        public CatalogVarCollectionEventArgs(CatalogVariableCollection src) {
            this.Source = src;
        }
    }

    public class CatalogVarModelEventArgs : CatalogVarCollectionEventArgs {
        public readonly IList<KeyValuePair<string, _ElementState>> Modifications;
        public CatalogVarModelEventArgs(CatalogVariableCollection src, IList<KeyValuePair<string, _ElementState>> list)
            : base(src) {
            this.Modifications = list;
        }

        CatalogVarCollectionElement this[string key] {
            get {
                return Source[key];
            }
        }
    }

    public class CatalogVarViewEventArgs : CatalogVarCollectionEventArgs {
        public readonly CatalogVarCollectionElement Element;
        public readonly CatalogVariableEventArgs CatalogVariableEventArgs;
        public CatalogVarViewEventArgs(CatalogVariableCollection src, CatalogVarCollectionElement elm, CatalogVariableEventArgs evt) : base(src) {
            this.CatalogVariableEventArgs = evt;
            this.Element = elm;
        }

        public ICatalogVariable CatalogVariable {
            get {
                return (CatalogVariableEventArgs != null) ? CatalogVariableEventArgs.Source : null;
            }
        }

        public object CurrentValue {
            get {
                return (CatalogVariableEventArgs != null) ? CatalogVariableEventArgs.Source.Current : null;
            }
        }

        public string ElementName {
            get {
                return Element.Name;
            }
        }
    }
}
