/*
 * Option.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.Globalization;
using Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;
using _Public = Eagle._Components.Public;

namespace Eagle._Components.Public
{
    [ObjectId("3081850b-bbde-4b8f-bc24-24513df11f2d")]
    public sealed class Option :
#if ISOLATED_INTERPRETERS || ISOLATED_PLUGINS
        ScriptMarshalByRefObject,
#endif
        IOption
    {
        #region Private Constants
        private static readonly char OptionCharacter = Characters.MinusSign;
        private static readonly string OptionPrefix = OptionCharacter.ToString();
        #endregion

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

        #region Public Constants
        public static readonly string EndOfOptions = "--";
        #endregion

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

        #region Private Constants
        //
        // NOTE: How many list elements are required when creating an option
        //       from a string?
        //
        private const int RequiredElementCount = 5;

        //
        // NOTE: What is the element index for the value of the option when
        //       creating an option from a string?
        //
        private const int ValueElementIndex = 5;
        #endregion

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

        #region Public Constructors
        public Option(
            Type type,
            OptionFlags flags,
            int groupIndex,
            int index,
            string name,
            Variant value
            )
        {
            this.kind = IdentifierKind.Option;
            this.name = name;
            this.description = null;
            this.clientData = null;
            this.type = type;
            this.flags = flags;
            this.groupIndex = groupIndex;
            this.index = index;
            this.value = value;
        }
        #endregion

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

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

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

        #region IIdentifier Members
        private IdentifierKind kind;
        public IdentifierKind Kind
        {
            get { return kind; }
            set { kind = value; }
        }

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

        private Guid id;
        public Guid Id
        {
            get { return id; }
            set { id = value; }
        }

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

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

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

        private string group;
        public string Group
        {
            get { return group; }
            set { group = value; }
        }

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

        private string description;
        public string Description
        {
            get { return description; }
            set { description = value; }
        }
        #endregion

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

        #region IHaveClientData Members
        private IClientData clientData;
        public IClientData ClientData
        {
            get { return clientData; }
            set { clientData = value; }
        }
        #endregion

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

        #region IOption Members
        private Type type;
        public Type Type
        {
            get { return type; }
            set { type = value; }
        }

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

        private OptionFlags flags;
        public OptionFlags Flags
        {
            get { return flags; }
            set { flags = value; }
        }

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

        //
        // NOTE: Which logical option grouping does this belong to (e.g.
        //       [lsort] has string comparison types like "ascii",
        //       "dictionary", "integer", and "real", and ordering types like
        //       "ascending" / "descending").
        //
        private int groupIndex;
        public int GroupIndex
        {
            get { return groupIndex; }
            set { groupIndex = value; }
        }

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

        private int index;
        public int Index
        {
            get { return index; }
            set { index = value; }
        }

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

        private Variant value;
        public Variant Value
        {
            get { return value; }
            set { this.value = value; }
        }

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

        public object InnerValue
        {
            get { return (value != null) ? value.Value : null; }
        }

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

        public bool HasFlags(
            OptionFlags flags,
            bool all
            )
        {
            return FlagOps.HasFlags(this.flags, flags, all);
        }

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

        public bool IsStrict(
            OptionDictionary options /* NOT USED */
            )
        {
            return HasFlags(OptionFlags.Strict, true);
        }

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

        public bool IsNoCase(
            OptionDictionary options /* NOT USED */
            )
        {
            return HasFlags(OptionFlags.NoCase, true);
        }

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

        public bool IsUnsafe(
            OptionDictionary options /* NOT USED */
            )
        {
            return HasFlags(OptionFlags.Unsafe, true);
        }

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

        public bool IsIgnored(
            OptionDictionary options /* NOT USED */
            )
        {
            return HasFlags(OptionFlags.Ignored, true);
        }

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

        public bool MustHaveValue(
            OptionDictionary options /* NOT USED */
            )
        {
            return HasFlags(OptionFlags.MustHaveValue, true);
        }

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

        public bool CanBePresent(
            OptionDictionary options, /* NOT USED */
            ref Result error
            )
        {
            if (HasFlags(OptionFlags.Unsupported, true))
            {
                error = String.Format(
                    "option \"{0}\" not supported for this platform",
                    name);

                return false;
            }

            if (HasFlags(OptionFlags.Disabled, true))
            {
                error = String.Format(
                    "option \"{0}\" is disabled",
                    name);

                return false;
            }

            return true;
        }

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

        public bool IsPresent(
            OptionDictionary options, /* NOT USED */
            ref Variant value
            )
        {
            if (HasFlags(OptionFlags.Present, true))
            {
                if (MustHaveValue(options))
                    value = this.value;

                return true;
            }

            return false;
        }

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

        public void SetPresent(
            OptionDictionary options,
            bool present,
            int index,
            Variant value
            )
        {
            if (present)
            {
                this.flags |= OptionFlags.Present;
                this.index = index;
                this.value = value;
            }
            else
            {
                this.flags &= ~OptionFlags.Present;
                this.index = _Constants.Index.Invalid;
                this.value = null;
            }

            //
            // NOTE: Now mark all the other options in this option group as 
            //       "not present".
            //
            if ((options != null) && (options.Values != null))
            {
                foreach (IOption option in options.Values)
                {
                    if ((option != null) &&
                        !Object.ReferenceEquals(option, this) &&
                        (option.GroupIndex != _Constants.Index.Invalid) &&
                        (option.GroupIndex == this.groupIndex))
                    {
                        //
                        // NOTE: Only modify the flags since that is how we detect 
                        //       whether the option is considered to be "present".
                        //
                        option.Flags &= ~OptionFlags.Present;
                    }
                }
            }
        }

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

        public string ToString(
            OptionFlags flags
            )
        {
            string result;

            if (FlagOps.HasFlags(flags, OptionFlags.MustHaveValue, true))
            {
                if (FlagOps.HasFlags(flags, OptionFlags.MustBeSecureString, true))
                    result = "secure string";
#if NATIVE && TCL
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeTclInterpreter, true))
                    result = "tcl interpreter";
#endif
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeRelativeNamespace, true))
                    result = "relative namespace";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeAbsoluteNamespace, true))
                    result = "absolute namespace";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeOption, true))
                    result = "option";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeAlias, true))
                    result = "alias";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeReturnCodeList, true))
                    result = "return code list";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeVersion, true))
                    result = "version";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeTypeList, true))
                    result = "type list";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeType, true))
                    result = "type";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeInterpreter, true))
                    result = "interpreter";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeObject, true))
                    result = "object";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeValue, true))
                    result = "numeric";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeMatchMode, true))
                    result = "match mode";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeDictionary, true))
                    result = "dictionary";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeList, true))
                    result = "list";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeTimeSpan, true))
                    result = "time-span";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeDateTime, true))
                    result = "date-time";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeGuid, true))
                    result = "guid";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeEnum, true))
                    result = (type != null) ?
                        String.Format("{0} enumeration",
                            FormatOps.TypeNameOrFullName(type)) :
                        "enumeration";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeReturnCode, true))
                    result = "return code";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeLevel, true))
                    result = "level";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeIndex, true))
                    result = "index";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeWideInteger, true))
                    result = "wide integer";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeInteger, true))
                    result = "integer";
                else if (FlagOps.HasFlags(flags, OptionFlags.MustBeBoolean, true))
                    result = "boolean";
                else
                    result = "string";
            }
            else if (FlagOps.HasFlags(flags, OptionFlags.EndOfOptions, true))
            {
                result = "end of options";
            }
            else
            {
                result = "nothing";
            }

            return result;
        }

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

        public StringList ToList(
            IOption option
            )
        {
            StringList list = new StringList();

            if (option == null)
                option = this;

            list.Add(option.Name);

            list.Add((option.Value != null) ?
                option.Value.ToString() : null);

            return list;
        }

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

        public string ToString(
            IOption option
            )
        {
            return GenericOps<string>.ListToString(
                ToList(option), _Constants.Index.Invalid,
                _Constants.Index.Invalid, ToStringFlags.None,
                Characters.Space.ToString(), null, false);
        }
        #endregion

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

        #region Public Static Methods
        public static bool IsEndOfOptions(
            string text
            )
        {
            return !String.IsNullOrEmpty(text) &&
                !String.IsNullOrEmpty(EndOfOptions) &&
                (text.Length == EndOfOptions.Length) &&
                (String.Compare(text, EndOfOptions,
                    StringOps.SystemStringComparisonType) == 0);
        }

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

        public static bool LooksLikeOption(
            string text
            )
        {
            return !String.IsNullOrEmpty(text) &&
                (text[0] == OptionCharacter);
        }

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

        public static string FormatOption(
            string name,
            bool prefix
            )
        {
            if (String.IsNullOrEmpty(name))
                return name;

            return String.Format("{0}{1}",
                prefix ? OptionPrefix : String.Empty, name);
        }

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

        public static IOption 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)
            {
                if (list.Count >= RequiredElementCount)
                {
                    Type type = null;
                    ResultList errors = null;

                    if (_Public.Value.GetType(
                            interpreter, list[0], appDomain,
                            _Public.Value.GetTypeValueFlags(
                                strict, verbose, noCase), cultureInfo,
                            ref type, ref errors) != ReturnCode.Ok)
                    {
                        error = errors;
                        return null;
                    }

                    object enumValue = EnumOps.TryParseFlagsEnum(
                        interpreter, typeof(OptionFlags), null, list[1],
                        cultureInfo, strict, noCase, ref error);

                    if (!(enumValue is OptionFlags))
                        return null;

                    int groupIndex = 0;

                    if (_Public.Value.GetInteger2(
                            list[2], ValueFlags.AnyInteger, cultureInfo,
                            ref groupIndex, ref error) != ReturnCode.Ok)
                    {
                        return null;
                    }

                    int index = 0;

                    if (_Public.Value.GetInteger2(
                            list[3], ValueFlags.AnyInteger, cultureInfo,
                            ref index, ref error) != ReturnCode.Ok)
                    {
                        return null;
                    }

                    string name = list[4];

                    if (name == null)
                    {
                        error = "invalid option name";
                        return null;
                    }

                    Variant value = null;

                    if (list.Count > ValueElementIndex)
                        value = new Variant(list[ValueElementIndex]);

                    return new Option(type, (OptionFlags)enumValue,
                        groupIndex, index, name, value);
                }
                else
                {
                    error = String.Format(
                        "cannot create option, only {0} of {1} " +
                        "required elements were specified", list.Count,
                        RequiredElementCount);
                }
            }

            return null;
        }
        #endregion
    }
}
