/*
 * OptionDictionary.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.Generic;
using System.Globalization;

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

using Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Interfaces.Public;

namespace Eagle._Containers.Public
{
    //
    // TODO: Centralize ALL options using ArgumentListOptionDictionary
    //       from a ranged ArgumentList to OptionDictionary.
    //
#if SERIALIZATION
    [Serializable()]
#endif
    [ObjectId("755e6bb2-b7e3-42df-bc4e-81610901e093")]
    public sealed class OptionDictionary : Dictionary<string, IOption>
    {
        #region Public Constructors
        public OptionDictionary()
            : base()
        {
            // do nothing.
        }

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

        public OptionDictionary(
            IEnumerable<IOption> collection
            )
            : this()
        {
            foreach (IOption item in collection)
                Add(item);
        }

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

        public OptionDictionary(
            IEnumerable<IOption> collection1,
            IEnumerable<IOption> collection2
            )
            : this()
        {
            foreach (IOption item in collection1)
                Add(item);

            foreach (IOption item in collection2)
                Add(item);
        }
        #endregion

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

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

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

        #region Static "Factory" Methods
        public static OptionDictionary FromString(
            Interpreter interpreter,
            string text,
            AppDomain appDomain,
            bool strict,
            bool verbose,
            bool noCase,
            CultureInfo cultureInfo,
            ref Result error
            )
        {
            StringList list = null;

            if (Parser.SplitList(
                    interpreter, text, 0, Length.Invalid, true,
                    ref list, ref error) == ReturnCode.Ok)
            {
                OptionDictionary options = new OptionDictionary();

                foreach (string element in list)
                {
                    IOption option = Option.FromString(
                        interpreter, element, appDomain, strict,
                        verbose, noCase, cultureInfo, ref error);

                    if (option == null)
                        return null;

                    options.Add(option);
                }

                return options;
            }

            return null;
        }
        #endregion

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

        #region Option Add Methods
        public void Add(
            IOption item
            )
        {
            this.Add(item.Name, item);
        }
        #endregion

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

        #region Option Has (Is Available) Methods
        public bool Has(string name)
        {
            return Has(this, name);
        }

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

        public static bool Has(OptionDictionary options, string name)
        {
            return (options != null) ? options.ContainsKey(name) : false;
        }
        #endregion

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

        #region Option IsPresent (Is Set) Methods
        public bool IsPresent(
            string name
            )
        {
            return IsPresent(name, false);
        }

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

        public bool IsPresent(
            string name,
            ref Variant value
            )
        {
            int index = Index.Invalid;

            return IsPresent(name, ref value, ref index);
        }

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

        public bool IsPresent(
            string name,
            ref Variant value,
            ref int index
            )
        {
            return IsPresent(name, false, ref value, ref index);
        }

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

        public bool IsPresent(
            string name,
            bool noCase
            )
        {
            Variant value = null;

            return IsPresent(name, noCase, ref value);
        }

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

        public bool IsPresent(
            string name,
            bool noCase,
            ref Variant value
            )
        {
            int index = Index.Invalid;

            return IsPresent(name, noCase, ref value, ref index);
        }

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

        public bool IsPresent(
            string name,
            bool noCase,
            ref Variant value,
            ref int index
            )
        {
            return IsPresent(this, name, noCase, ref value, ref index);
        }

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

        public static bool IsPresent(
            OptionDictionary options,
            string name,
            bool noCase
            )
        {
            Variant value = null;

            return IsPresent(options, name, noCase, ref value);
        }

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

        public static bool IsPresent(
            OptionDictionary options,
            string name,
            bool noCase,
            ref Variant value
            )
        {
            int index = Index.Invalid;

            return IsPresent(options, name, noCase, ref value, ref index);
        }

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

        public static bool IsPresent(
            OptionDictionary options,
            string name,
            bool noCase,
            ref Variant value,
            ref int index
            )
        {
            return IsPresent(
                options, name, noCase, true, ref value, ref index);
        }

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

        #region Internal
        internal bool CheckPresent(
            string name
            )
        {
            Variant value = null;

            return CheckPresent(name, ref value);
        }

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

        internal bool CheckPresent(
            string name,
            ref Variant value
            )
        {
            int index = Index.Invalid;

            return IsPresent(this, name, false, false, ref value, ref index);
        }
        #endregion

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

        #region Private
        private static bool IsPresent(
            OptionDictionary options,
            string name,
            bool noCase,
            bool strict,
            ref Variant value,
            ref int index
            )
        {
            if (options != null)
            {
                if (name != null)
                {
                    if (noCase)
                    {
                        //
                        // HACK: Perform a linear search of the options.  We
                        //       should not need to do this since the options
                        //       are in a dictionary; however, we want to
                        //       preserve the "case-sensitive" semantics unless
                        //       otherwise requested by the caller.
                        //
                        bool found = false;

                        foreach (KeyValuePair<string, IOption> pair in options)
                        {
                            if (String.Compare(pair.Key, 0, name, 0, name.Length,
                                    StringOps.SystemNoCaseStringComparisonType) == 0)
                            {
                                found = true;

                                IOption option = pair.Value;

                                if ((option != null) &&
                                    option.IsPresent(options, ref value))
                                {
                                    index = option.Index;
                                    return true;
                                }
                            }
                        }

                        if (strict && !found)
                        {
                            //
                            // NOTE: This should not really happen, issue a
                            //       debug trace.
                            //
                            TraceOps.DebugTrace(String.Format(
                                "{0}: IsPresent: {1}{2}",
                                GlobalState.GetCurrentThreadId(),
                                BadOption(options, name),
                                Environment.NewLine),
                                typeof(OptionDictionary).Name);
                        }
                    }
                    else
                    {
                        IOption option;

                        if (options.TryGetValue(name, out option))
                        {
                            if ((option != null) &&
                                option.IsPresent(options, ref value))
                            {
                                index = option.Index;
                                return true;
                            }
                        }
                        else if (strict)
                        {
                            //
                            // NOTE: This should not really happen, issue a
                            //       debug trace.
                            //
                            TraceOps.DebugTrace(String.Format(
                                "{0}: IsPresent: {1}{2}",
                                GlobalState.GetCurrentThreadId(),
                                BadOption(options, name),
                                Environment.NewLine),
                                typeof(OptionDictionary).Name);
                        }
                    }
                }
            }

            return false;
        }
        #endregion
        #endregion

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

        #region Option Setting Methods
        public bool SetPresent(
            string name,
            bool present,
            int index,
            Variant value
            )
        {
            return SetPresent(this, name, present, index, value);
        }

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

        public static bool SetPresent(
            OptionDictionary options,
            string name,
            bool present,
            int index,
            Variant value
            )
        {
            if (options != null)
            {
                if (name != null)
                {
                    IOption option;

                    if (options.TryGetValue(name, out option))
                    {
                        if (option != null)
                        {
                            option.SetPresent(options, present, index, value);

                            return true;
                        }
                    }
                }
            }

            return false;
        }
        #endregion

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

        #region Option Lookup Methods
        public ReturnCode TryResolve(
            string name,
            bool strict,
            bool noCase,
            ref IOption option,
            ref Result error
            )
        {
            return TryResolve(this, name, strict, noCase, ref option, ref error);
        }

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

        public static ReturnCode TryResolve(
            OptionDictionary options,
            string name,
            bool strict,
            bool noCase,
            ref IOption option,
            ref Result error
            )
        {
            if (options != null)
            {
                if (name != null)
                {
                    string exactName = null;
                    StringList list = new StringList();

                    foreach (KeyValuePair<string, IOption> pair in options)
                    {
                        string key = pair.Key;
                        IOption value = pair.Value;
                        bool match;

                        if (noCase || ((value != null) && value.IsNoCase(options)))
                        {
                            match = (String.Compare(key, 0, name, 0, name.Length,
                                StringOps.SystemNoCaseStringComparisonType) == 0);
                        }
                        else
                        {
                            match = (String.Compare(key, 0, name, 0, name.Length,
                                StringOps.SystemStringComparisonType) == 0);
                        }

                        if (match)
                        {
                            //
                            // NOTE: Was the key valid (this should always succeed).
                            //
                            if (key != null)
                            {
                                //
                                // NOTE: It was a match; however, was it an exact match?
                                //
                                if (key.Length == name.Length)
                                    //
                                    // NOTE: Preserve match, it may differ in case.
                                    //
                                    exactName = key;

                                //
                                // NOTE: Was it an exact match or did we match at least one 
                                //       character in a partial match?
                                //
                                if ((key.Length == name.Length) || (name.Length > 0))
                                    //
                                    // NOTE: Store the exact or partial match in the results 
                                    //       dictionary.
                                    //
                                    list.Add(key);
                            }
                        }
                    }

                    //
                    // NOTE: If there was an exact match, just use it.
                    //
                    if (exactName != null)
                    {
                        //
                        // NOTE: Normal case, an exact option match was found.
                        //
                        option = options[exactName];

                        return ReturnCode.Ok;
                    }
                    else if (list.Count == 1)
                    {
                        //
                        // NOTE: Normal case, exactly one option partially matched.
                        //
                        option = options[list[0]];

                        return ReturnCode.Ok;
                    }
                    else if (list.Count > 1)
                    {
                        //
                        // NOTE: They specified an ambiguous option.
                        //
                        error = AmbiguousOption(options, name, list);
                    }
                    else if (strict)
                    {
                        //
                        // NOTE: They specified a non-existent option.
                        //
                        error = BadOption(options, name);
                    }
                    else
                    {
                        //
                        // NOTE: Non-strict mode, leave the original option value 
                        //       unchanged and let the caller deal with it.
                        //
                        return ReturnCode.Ok;
                    }
                }
                else
                {
                    error = "invalid option name";
                }
            }
            else
            {
                error = "invalid options";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region ArgumentList Building Methods
        public ReturnCode ToArgumentList(
            ref ArgumentList arguments,
            ref Result error
            )
        {
            return ToArgumentList(this, ref arguments, ref error);
        }

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

        public static ReturnCode ToArgumentList(
            OptionDictionary options,
            ref ArgumentList arguments,
            ref Result error
            )
        {
            if (options != null)
            {
                if (options.Count > 0)
                {
                    if (arguments == null)
                        arguments = new ArgumentList();

                    IOption endOption = null;

                    foreach (KeyValuePair<string, IOption> pair in options)
                    {
                        IOption option = pair.Value;

                        if (option == null)
                            continue;

                        if (option.IsIgnored(options))
                            continue;

                        if (!option.HasFlags(OptionFlags.EndOfOptions, true))
                        {
                            Variant value = null;

                            if (!option.IsPresent(options, ref value))
                                continue;

                            if (!option.CanBePresent(options, ref error))
                                return ReturnCode.Error;

                            arguments.Add(new Argument(option.Name));

                            if (option.MustHaveValue(options))
                                arguments.Add(new Argument(value));
                        }
                        else
                        {
                            //
                            // NOTE: This option must be processed last; however,
                            //       we still need to keep track of it now until
                            //       that time.
                            //
                            endOption = option;
                        }
                    }

                    if ((endOption != null) && !endOption.IsIgnored(options))
                    {
                        Variant value = null;

                        if (endOption.IsPresent(options, ref value))
                        {
                            if (!endOption.CanBePresent(options, ref error))
                                return ReturnCode.Error;

                            arguments.Add(new Argument(endOption.Name));

                            if (endOption.MustHaveValue(options))
                                arguments.Add(new Argument(value));
                        }
                    }
                }

                return ReturnCode.Ok;
            }
            else
            {
                error = "invalid options";
            }

            return ReturnCode.Error;
        }
        #endregion

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

        #region Error Message Methods
        public static Result AmbiguousOption(
            OptionDictionary options,
            string name,
            StringList list
            )
        {
            if ((options != null) && (list != null))
                return String.Format(
                    "ambiguous option \"{0}\": must be {1}", name,
                    // Common.Generic<string>.ListToEnglish(
                    //     new StringSortedList(options.Keys),
                    GenericOps<string>.ListToEnglish(
                        new StringSortedList(list),
                        String.Empty, ", ", "or "));

            return String.Format(
                "ambiguous option \"{0}\"",
                name); // FIXME: Fallback here?
        }

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

        public static Result BadOption(
            OptionDictionary options,
            string name
            )
        {
            if (options != null)
                return String.Format(
                    "bad option \"{0}\": must be {1}",
                    name, GenericOps<string>.ListToEnglish(
                        new StringSortedList(options.Keys),
                        String.Empty, ", ", "or "));

            return String.Format(
                "bad option \"{0}\"",
                name); // FIXME: Fallback here?
        }
        #endregion

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

        #region ToString Methods
        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
    }
}
