/*
 * ElementDictionary.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

#if SERIALIZATION
using System.Runtime.Serialization;
#endif

using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Components.Public;
using Eagle._Constants;

namespace Eagle._Containers.Public
{
#if SERIALIZATION
    [Serializable()]
#endif
    [ObjectId("93a6e29b-64cb-418d-9454-928e1dc05245")]
    public sealed class ElementDictionary : Dictionary<string, object>
    {
        #region Private Static Data
#if !MONO
        private static readonly object syncRoot = new object();
#endif

        ///////////////////////////////////////////////////////////////////////

        private static int DefaultCapacity = 0;

        ///////////////////////////////////////////////////////////////////////

#if !MONO
        private static readonly string BucketsFieldName = "buckets";
        private static FieldInfo BucketsFieldInfo = null;
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Data
        private EventWaitHandle variableEvent;
        private Dictionary<string, VariableFlags> variableFlags;
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Constructors
        public ElementDictionary(
            EventWaitHandle variableEvent
            )
            : this(variableEvent, DefaultCapacity)
        {
            // do nothing.
        }

        ///////////////////////////////////////////////////////////////////////

        public ElementDictionary(
            EventWaitHandle variableEvent,
            int capacity
            )
            : base(capacity)
        {
            Initialize(variableEvent);
        }

        ///////////////////////////////////////////////////////////////////////

        public ElementDictionary(
            EventWaitHandle variableEvent,
            IDictionary dictionary
            )
            : this(variableEvent, DefaultCapacity)
        {
            foreach (DictionaryEntry entry in dictionary)
                this.Add(entry.Key.ToString(), entry.Value);
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Protected Constructors
#if SERIALIZATION
        private ElementDictionary(
            SerializationInfo info,
            StreamingContext context
            )
            : base(info, context)
        {
            // do nothing.
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Methods
        private void Initialize(
            EventWaitHandle variableEvent
            )
        {
            this.variableEvent = variableEvent;
            variableFlags = new Dictionary<string, VariableFlags>();
        }

        ///////////////////////////////////////////////////////////////////////

        internal bool ResetValue(
            Interpreter interpreter,
            string key,
            bool zero
            )
        {
#if !MONO && NATIVE && WINDOWS
            object value;

            if (zero && (key != null) && TryGetValue(key, out value))
            {
                ReturnCode zeroCode;
                Result zeroError = null;

                if (zero && (value is string) && (interpreter != null) &&
                    interpreter.HasZeroString())
                {
                    zeroCode = StringOps.ZeroString(
                        (string)value, ref zeroError);

                    if (zeroCode != ReturnCode.Ok)
                        DebugOps.Complain(interpreter, zeroCode, zeroError);
                }
            }
#endif

            return Remove(key);
        }

        ///////////////////////////////////////////////////////////////////////

        internal void ResetValue(
            Interpreter interpreter,
            bool zero
            )
        {
#if !MONO && NATIVE && WINDOWS
            if (zero && (interpreter != null) && interpreter.HasZeroString())
            {
                foreach (KeyValuePair<string, object> pair in this)
                {
                    object value = pair.Value;

                    if (value is string)
                    {
                        ReturnCode zeroCode;
                        Result zeroError = null;

                        zeroCode = StringOps.ZeroString(
                            (string)value, ref zeroError);

                        if (zeroCode != ReturnCode.Ok)
                            DebugOps.Complain(interpreter, zeroCode, zeroError);
                    }
                    else if (value is Argument)
                    {
                        ((Argument)value).ResetValue(interpreter, zero);
                    }
                    else if (value is Result)
                    {
                        ((Result)value).ResetValue(interpreter, zero);
                    }
                }
            }
#endif

            Clear();
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Methods
        public VariableFlags GetFlags(
            string key,
            VariableFlags @default
            )
        {
            if ((key != null) && (variableFlags != null))
            {
                VariableFlags value;

                if (variableFlags.TryGetValue(key, out value))
                    return value;
            }

            return @default;
        }

        ///////////////////////////////////////////////////////////////////////

        public bool ChangeFlags(
            string key,
            VariableFlags value,
            bool add,
            bool force,
            ref bool notify
            )
        {
            if ((key != null) && (variableFlags != null))
            {
                VariableFlags oldValue;
                VariableFlags newValue;

                if (variableFlags.TryGetValue(key, out oldValue))
                {
                    if (add)
                    {
                        newValue = oldValue | value;

                        variableFlags[key] = newValue;

                        if (notify && EntityOps.OnFlagsChanged(
                                variableEvent, oldValue, newValue))
                        {
                            notify = false;
                        }

                        return true;
                    }
                    else
                    {
                        newValue = oldValue & ~value;

                        if (newValue != VariableFlags.None)
                        {
                            variableFlags[key] = newValue;

                            if (notify && EntityOps.OnFlagsChanged(
                                    variableEvent, oldValue, newValue))
                            {
                                notify = false;
                            }

                            return true;
                        }

                        if (notify && EntityOps.OnFlagsChanged(
                                variableEvent, oldValue, newValue))
                        {
                            notify = false;
                        }

                        return variableFlags.Remove(key);
                    }
                }
                else if (add || force)
                {
                    newValue = add ? value : VariableFlags.None;

                    variableFlags.Add(key, newValue);

                    if (notify && EntityOps.OnFlagsChanged(
                            variableEvent, oldValue, newValue))
                    {
                        notify = false;
                    }

                    return true;
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////

        public void Add(
            IDictionary<string, object> dictionary
            )
        {
            foreach (KeyValuePair<string, object> pair in dictionary)
                this.Add(pair.Key, pair.Value);
        }

        ///////////////////////////////////////////////////////////////////////

        public string GetRandom(
            RandomNumberGenerator rng, /* in */
            ref Result error           /* out */
            )
        {
            if (rng == null)
            {
                error = "random number generator not available";
                return null;
            }

            if (this.Count == 0)
            {
                error = "no elements in array";
                return null;
            }

            byte[] bytes = new byte[sizeof(int)];

            rng.GetBytes(bytes);

            int index = BitConverter.ToInt32(bytes, 0);

            //
            // FIXME: *PERF* This is really bad for performance.
            //
            StringList keys = new StringList(this.Keys);

            return keys[Math.Abs(index) % this.Count];
        }

        ///////////////////////////////////////////////////////////////////////

        //
        // NOTE: For interactive introspection use via the default host only.
        //       Do not rely on this method in other code.
        //
        public int GetCapacity()
        {
#if !MONO
            if (!CommonOps.Runtime.IsMono())
            {
                lock (syncRoot)
                {
                    if (BucketsFieldInfo == null)
                    {
                        BucketsFieldInfo = typeof(Dictionary<string, object>).
                            GetField(BucketsFieldName,
                                MarshalOps.PrivateInstanceGetFieldBindingFlags);
                    }

                    if (BucketsFieldInfo != null)
                    {
                        int[] buckets = BucketsFieldInfo.GetValue(this) as int[];

                        if (buckets != null)
                            return buckets.Length;
                    }
                }
            }
#endif

            return DefaultCapacity;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region ToString Methods
        public string KeysToString(
            MatchMode mode,
            string pattern,
            bool noCase,
            RegexOptions regExOptions
            )
        {
            StringList list = GenericOps<string, object>.KeysAndValues(
                this, false, true, false, mode, pattern, null, null, null,
                null, noCase, regExOptions) as StringList;

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), null, false);
        }

        ///////////////////////////////////////////////////////////////////////

        public string KeysToString(
            string separator
            )
        {
            StringList list = new StringList(this.Keys);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                separator, null, false);
        }

        ///////////////////////////////////////////////////////////////////////

        public string KeysToString(
            string pattern,
            bool noCase
            )
        {
            StringList list = new StringList(this.Keys);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), pattern, noCase);
        }

        ///////////////////////////////////////////////////////////////////////

        public string KeysToString(
            string pattern,
            RegexOptions regExOptions
            )
        {
            StringList list = new StringList(this.Keys);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), pattern, regExOptions);
        }

        ///////////////////////////////////////////////////////////////////////

        public string ValuesToString(
            MatchMode mode,
            string pattern,
            bool noCase,
            RegexOptions regExOptions
            )
        {
            StringList list = GenericOps<string, object>.KeysAndValues(
                this, false, false, true, mode, null, pattern, null, null,
                null, noCase, regExOptions) as StringList;

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), null, false);
        }

        ///////////////////////////////////////////////////////////////////////

        public string ValuesToString(
            string pattern,
            bool noCase
            )
        {
            StringList list = new StringList(this.Values);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), pattern, noCase);
        }

        ///////////////////////////////////////////////////////////////////////

        public string ValuesToString(
            string pattern,
            RegexOptions regExOptions
            )
        {
            StringList list = new StringList(this.Values);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), pattern, regExOptions);
        }

        ///////////////////////////////////////////////////////////////////////

        public string KeysAndValuesToString(
            string pattern,
            bool noCase
            )
        {
            StringList list = GenericOps<string, object>.KeysAndValues(
                this, false, true, true, StringOps.DefaultMatchMode, pattern,
                null, null, null, null, noCase, RegexOptions.None) as StringList;

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), null, false);
        }

        ///////////////////////////////////////////////////////////////////////

        public string KeysAndValuesToString(
            string pattern,
            RegexOptions regExOptions
            )
        {
            StringList list = GenericOps<string, object>.KeysAndValues(
                this, false, true, true, MatchMode.RegExp, pattern, null, null,
                null, null, false, regExOptions) as StringList;

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), null, false);
        }

        ///////////////////////////////////////////////////////////////////////

        public string ToString(
            string pattern,
            bool noCase
            )
        {
            StringList list = new StringList(this.Keys);

            return GenericOps<string>.ListToString(
                list, Index.Invalid, Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), pattern, noCase);
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region System.Object Overrides
        public override string ToString()
        {
            return ToString(null, false);
        }
        #endregion
    }
}
