/*
 * ArgumentList.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.Text;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Interfaces.Public;

namespace Eagle._Containers.Public
{
#if SERIALIZATION
    [Serializable()]
#endif
    [ObjectId("65ed4894-90ef-49bf-8744-2a7c3406af55")]
    public sealed class ArgumentList : List<Argument>
    {
        #region Private Static Data
#if CACHE_ARGUMENTLIST_TOSTRING && CACHE_STATISTICS
        private static readonly int[] cacheCounts =
            new int[(int)CacheCountType.SizeOf];
#endif
        #endregion

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

        #region Private Constants
        private static readonly string DefaultSeparator =
            Characters.Space.ToString();
        #endregion

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

        #region Private Data
#if CACHE_ARGUMENTLIST_TOSTRING
        private string @string; /* CACHE */
#endif
        #endregion

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

        #region Private Constructors
        private ArgumentList()
            : base()
        {
            // do nothing.
        }
        #endregion

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

        #region Public Constructors
        public ArgumentList(
            params object[] objects
            )
            : base()
        {
            int length = objects.Length;

            if (length > 0)
            {
                for (int index = 0; index < length; index++)
                {
                    object @object = objects[index];

                    if (@object is ArgumentList)
                    {
                        //
                        // NOTE: They supplied an argument list as a parameter.
                        //       Add the entire list to this list.
                        //
                        this.AddRange((ArgumentList)@object);
                    }
                    else if (@object is StringList)
                    {
                        //
                        // NOTE: They supplied a string list as a parameter.
                        //       Add the entire list to this list.
                        //
                        this.AddRange((StringList)@object);
                    }
                    else if (@object is IEnumerable<string>)
                    {
                        //
                        // NOTE: They supplied a string array as a parameter.
                        //       Add all the elements to this list.
                        //
                        this.AddRange((IEnumerable<string>)@object);
                    }
                    else if ((@object is ICollection) || (@object is IList))
                    {
                        //
                        // NOTE: They supplied an collection or list [of some
                        //       kind] as a parameter.  Add all supported
                        //       elements to this list.
                        //
                        this.AddRange(
                            (IEnumerable)@object, true, true, false, false);
                    }
                    else
                    {
                        Argument argument;

                        if (@object is Argument)
                        {
                            argument = (Argument)@object;
                        }
                        else if (@object is string)
                        {
                            argument = new Argument((string)@object);
                        }
                        else if (@object != null)
                        {
                            argument = new Argument(@object.ToString());
                        }
                        else
                        {
                            argument = new Argument(Argument.NoValue);
                        }

                        this.Add(argument);
                    }
                }
            }
        }
        #endregion

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

        #region Internal Constructors
        internal ArgumentList(
            int capacity
            )
            : base(capacity)
        {
            // do nothing.
        }

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

        internal ArgumentList(
            IEnumerable<Argument> collection
            )
            : base(collection)
        {
            // do nothing.
        }

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

        internal ArgumentList(
            IEnumerable<string> collection
            )
            : this()
        {
            AddRange(collection);
        }

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

        internal ArgumentList(
            IEnumerable<string> collection,
            ArgumentFlags flags
            )
            : this()
        {
            foreach (string item in collection)
            {
                Argument argument;

                if (FlagOps.HasFlags(flags, ArgumentFlags.NameOnly, true))
                    argument = new Argument(flags, item);
                else
                    argument = new Argument(flags, Argument.NoName, item);

                this.Add(argument);
            }
        }

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

        internal ArgumentList(
            IEnumerable<IPair<string>> collection,
            ArgumentFlags flags
            )
            : this()
        {
            foreach (IPair<string> item in collection)
            {
                ArgumentFlags valueFlags = (item.Y != null) ?
                    ArgumentFlags.HasDefault : ArgumentFlags.None;

                Argument argument;

                if (FlagOps.HasFlags(flags, ArgumentFlags.NameOnly, true))
                    argument = new Argument(
                        flags | valueFlags, item.X, Argument.NoValue, item.Y);
                else
                    argument = new Argument(
                        flags | valueFlags, Argument.NoName, item.X, item.Y);

                this.Add(argument);
            }
        }
        #endregion

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

        #region Public Static Methods
        #region Argument Handling Methods
        public static ArgumentList NullIfEmpty(
            ArgumentList list,
            int firstIndex
            )
        {
            if (list == null)
                return null;

            if (firstIndex == Index.Invalid)
                firstIndex = 0;

            if ((firstIndex < 0) || (firstIndex >= list.Count))
                return null;

            //
            // NOTE: If there are elements beyond the first index or the
            //       element at the first index is not empty, then return
            //       the range starting from the first index; otherwise,
            //       return null.
            //
            if (((firstIndex + 1) < list.Count) ||
                !String.IsNullOrEmpty(list[firstIndex]))
            {
                return GetRange(list, firstIndex);
            }

            return null;
        }
        #endregion

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

        #region Range Methods
        public static ArgumentList GetRange(
            ArgumentList list,
            int firstIndex
            )
        {
            return GetRange(list, firstIndex,
                (list != null) ? (list.Count - 1) : Index.Invalid);
        }

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

        public static ArgumentList GetRange(
            IList list,
            int firstIndex,
            bool nullIfEmpty
            )
        {
            return GetRange(list, firstIndex,
                (list != null) ? (list.Count - 1) : Index.Invalid,
                nullIfEmpty);
        }

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

        public static ArgumentList GetRange(
            IList list,
            int firstIndex,
            int lastIndex
            )
        {
            return GetRange(list, firstIndex, lastIndex, false);
        }

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

        public static ArgumentList GetRange(
            IList list,
            int firstIndex,
            int lastIndex,
            bool nullIfEmpty
            )
        {
            ArgumentList range = null;

            if (firstIndex == Index.Invalid)
                firstIndex = 0;

            if (lastIndex == Index.Invalid)
                lastIndex = list.Count - 1;

            if ((list != null) && (!nullIfEmpty ||
                    ((list.Count > 0) && ((lastIndex - firstIndex) > 0))))
            {
                range = new ArgumentList();

                for (int index = firstIndex; index <= lastIndex; index++)
                {
                    if (list[index] != null)
                        range.Add(list[index].ToString());
                    else
                        range.Add(null);
                }
            }

            return range;
        }

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

        public static StringList GetRangeAsStringList(
            ArgumentList list,
            int firstIndex
            )
        {
            return GetRangeAsStringList(list, firstIndex,
                (list != null) ? (list.Count - 1) : Index.Invalid);
        }

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

        public static StringList GetRangeAsStringList(
            IList list,
            int firstIndex,
            int lastIndex
            )
        {
            StringList range = null;

            if (list != null)
            {
                range = new StringList();

                if (firstIndex == Index.Invalid)
                    firstIndex = 0;

                if (lastIndex == Index.Invalid)
                    lastIndex = list.Count - 1;

                for (int index = firstIndex; index <= lastIndex; index++)
                {
                    if (list[index] != null)
                        range.Add(list[index].ToString());
                    else
                        range.Add((string)null);
                }
            }

            return range;
        }
        #endregion
        #endregion

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

        #region Public Methods
        public void AddRange(
            IEnumerable collection,
            bool forceCopy,
            bool supportedOnly,
            bool toString,
            bool allowNull
            )
        {
#if CACHE_ARGUMENTLIST_TOSTRING
            InvalidateCachedString();
#endif

            foreach (object item in collection)
            {
                Argument argument = Argument.FromObject(
                    item, forceCopy, supportedOnly, toString);

                if (!allowNull && (argument == null))
                    continue;

                this.Add(argument);
            }
        }

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

        public void AddRange(
            IEnumerable<string> collection
            )
        {
#if CACHE_ARGUMENTLIST_TOSTRING
            InvalidateCachedString();
#endif

            foreach (string item in collection)
                this.Add(item);
        }

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

        public void InsertRange(
            int index,
            IEnumerable<string> collection
            )
        {
#if CACHE_ARGUMENTLIST_TOSTRING
            InvalidateCachedString();
#endif

            this.InsertRange(index, new ArgumentList(collection));
        }

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

        public bool IsVariadic(
            bool setFlags
            )
        {
            //
            // NOTE: Grab the count as we need to use it several
            //       times in this method.
            //
            int count = this.Count;

            //
            // NOTE: Does this argument list accept a variable
            //       numbers of arguments (COMPAT: Tcl)?
            //
            bool result = (count > 0) && (String.Compare(
                this[count - 1].Name, TclVars.Arguments,
                StringOps.SystemStringComparisonType) == 0);

            //
            // HACK: Flag the final argument as an "argument list"
            //       if necessary so that the syntax error message,
            //       if necessary, will be correct.
            //
            if (result)
            {
#if CACHE_ARGUMENTLIST_TOSTRING
                InvalidateCachedString();
#endif

                this[count - 1].Flags |= ArgumentFlags.ArgumentList;
            }

            return result;
        }

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

        public int GetOptionalCount()
        {
            //
            // NOTE: Return the total number of arguments in the list
            //       that are optional.  In order for an argument to
            //       be considered optional, it must meet the following
            //       criteria:
            //
            //       1. It must have a default value that is not null.
            //
            //       2. No non-optional arguments may occur after it
            //          in the argument list (COMPAT: Tcl).
            //
            int result = 0;

            //
            // NOTE: Grab the count as we need to use it several times in
            //       this method.
            //
            int count = this.Count;

            //
            // NOTE: Count all the arguments starting from the end of
            //       the list going backward that have a default value.
            //
            int index = IsVariadic(false) ? count - 2 : count - 1;

            for (; index >= 0; index--)
            {
                Argument argument = this[index];

                if ((argument != null) &&
                    FlagOps.HasFlags(argument.Flags,
                        ArgumentFlags.HasDefault, true))
                {
                    result++;
                }
                else
                {
                    break;
                }
            }

            return result;
        }

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

        public string ToRawString()
        {
            StringBuilder result = StringOps.NewStringBuilder();

            foreach (Argument element in this)
                result.Append(element);

            return result.ToString();
        }

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

        public string ToRawString(
            ToStringFlags toStringFlags,
            string separator
            )
        {
            StringBuilder result = StringOps.NewStringBuilder();

            foreach (Argument element in this)
            {
                if (element != null)
                {
                    if ((separator != null) && (result.Length > 0))
                        result.Append(separator);

                    result.Append(element.ToString(toStringFlags));
                }
            }

            return result.ToString();
        }

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

        public string ToString(
            ToStringFlags toStringFlags
            )
        {
            return ToString(toStringFlags, null, false);
        }

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

        public string ToString(
            ToStringFlags toStringFlags,
            string pattern,
            bool noCase
            )
        {
#if CACHE_ARGUMENTLIST_TOSTRING
            bool canUseCachedString = CanUseCachedString(
                toStringFlags, DefaultSeparator, pattern, noCase);

            if (canUseCachedString && (@string != null))
            {
#if CACHE_STATISTICS
                Interlocked.Increment(
                    ref cacheCounts[(int)CacheCountType.Hit]);
#endif

                return @string;
            }

#if CACHE_STATISTICS
            Interlocked.Increment(
                ref cacheCounts[(int)CacheCountType.Miss]);
#endif
#endif

            string result = GenericOps<Argument>.ListToString(
                this, Index.Invalid, Index.Invalid, toStringFlags,
                DefaultSeparator, pattern, noCase);

#if CACHE_ARGUMENTLIST_TOSTRING
            if (canUseCachedString)
                @string = result;
#endif

            return result;
        }

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

        public string ToString(
            string separator,
            string pattern,
            bool noCase
            )
        {
#if CACHE_ARGUMENTLIST_TOSTRING
            bool canUseCachedString = CanUseCachedString(
                ToStringFlags.None, separator, pattern, noCase);

            if (canUseCachedString && (@string != null))
            {
#if CACHE_STATISTICS
                Interlocked.Increment(
                    ref cacheCounts[(int)CacheCountType.Hit]);
#endif

                return @string;
            }

#if CACHE_STATISTICS
            Interlocked.Increment(
                ref cacheCounts[(int)CacheCountType.Miss]);
#endif
#endif

            string result = GenericOps<Argument>.ListToString(
                this, Index.Invalid, Index.Invalid, ToStringFlags.None,
                separator, pattern, noCase);

#if CACHE_ARGUMENTLIST_TOSTRING
            if (canUseCachedString)
                @string = result;
#endif

            return result;
        }
        #endregion

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

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

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

        #region Cached String Helper Methods
#if CACHE_ARGUMENTLIST_TOSTRING
        private void InvalidateCachedString()
        {
            @string = null;
        }

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

        private bool CanUseCachedString(
            ToStringFlags toStringFlags,
            string separator,
            string pattern,
            bool noCase
            )
        {
            if (toStringFlags != ToStringFlags.None)
                return false;

            if (!Parser.IsListSeparator(separator))
                return false;

            if (pattern != null)
                return false;

            return true;
        }

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

#if CACHE_STATISTICS
        public static bool HaveCacheCounts()
        {
            return FormatOps.HaveCacheCounts(cacheCounts);
        }

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

        public static string CacheToString()
        {
            return FormatOps.CacheCounts(cacheCounts);
        }
#endif

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

        #region System.Collections.Generic.ICollection<Argument> Overrides
        public new void Add(
            Argument item
            )
        {
            InvalidateCachedString();
            base.Add(item);
        }

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

        public new void Clear()
        {
            InvalidateCachedString();
            base.Clear();
        }

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

        public new bool Remove(
            Argument item
            )
        {
            InvalidateCachedString();
            return base.Remove(item);
        }
        #endregion

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

        #region System.Collections.Generic.IList<Argument> Overrides
        public new void Insert(
            int index,
            Argument item
            )
        {
            InvalidateCachedString();
            base.Insert(index, item);
        }

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

        public new void RemoveAt(
            int index
            )
        {
            InvalidateCachedString();
            base.RemoveAt(index);
        }

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

        public new Argument this[int index]
        {
            get { return base[index]; }
            set { InvalidateCachedString(); base[index] = value; }
        }
        #endregion

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

        #region System.Collections.Generic.List<Argument> Overrides
        public new void AddRange(
            IEnumerable<Argument> collection
            )
        {
            InvalidateCachedString();
            base.AddRange(collection);
        }

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

        public new void InsertRange(
            int index,
            IEnumerable<Argument> collection
            )
        {
            InvalidateCachedString();
            base.InsertRange(index, collection);
        }

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

        public new int RemoveAll(
            Predicate<Argument> match
            )
        {
            InvalidateCachedString();
            return base.RemoveAll(match);
        }

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

        public new void RemoveRange(
            int index,
            int count
            )
        {
            InvalidateCachedString();
            base.RemoveRange(index, count);
        }

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

        public new void Reverse()
        {
            InvalidateCachedString();
            base.Reverse();
        }

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

        public new void Reverse(
            int index,
            int count
            )
        {
            InvalidateCachedString();
            base.Reverse(index, count);
        }

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

        public new void Sort()
        {
            InvalidateCachedString();
            base.Sort();
        }

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

        public new void Sort(
            Comparison<Argument> comparison
            )
        {
            InvalidateCachedString();
            base.Sort(comparison);
        }

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

        public new void Sort(
            IComparer<Argument> comparer
            )
        {
            InvalidateCachedString();
            base.Sort(comparer);
        }

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

        public new void Sort(
            int index,
            int count,
            IComparer<Argument> comparer)
        {
            InvalidateCachedString();
            base.Sort(index, count, comparer);
        }
        #endregion
#endif
        #endregion
    }
}
