/*
 * AttributeFlags.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.Text;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Containers.Private;
using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
    [ObjectId("17b64eff-174f-4856-bafd-df94c759de17")]
    internal static class AttributeFlags
    {
        #region Public Constants
        public static readonly long NoKey = 0;
        #endregion

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

        #region Private Constants
        private static readonly char NameSepatator = Characters.Colon;

        private const char AddCharacter = Characters.PlusSign;
        private const char RemoveCharacter = Characters.MinusSign;
        private const char SetCharacter = Characters.EqualSign;

        private const char AllChars = Characters.Asterisk;
        private const char AllDigitChars = Characters.NumberSign;
        private const char AllAlphabetChars = Characters.ExclamationMark;
        private const char AllUpperAlphabetChars = Characters.DollarSign;
        private const char AllLowerAlphabetChars = Characters.AtSign;

        private static readonly int NameLength = 16; // (i.e. hexadecimal 64-bit integer)

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

        private static readonly string LegacyFlagFormat =
            "{{{0:X" + NameLength.ToString() + "}{1}}}";

        private static readonly string FlagFormat = "{{{0:X}:{1}}}";
        #endregion

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

        #region Private Helper Methods
        private static bool HexadecimalNameToKey(
            string name,
            ref long key
            )
        {
            if (name != null)
            {
                int length = name.Length;

                if ((length > 0) &&
                    Parser.ParseHexadecimal(name, 0, length, ref key) == length)
                {
                    return true;
                }
            }

            return false;
        }

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

        private static bool CharIsValidName(
            char character
            )
        {
            return Parser.IsHexadecimalDigit(character);
        }

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

        private static bool CharIsValidValue(
            char character
            )
        {
            return Parser.IsIdentifier(character);
        }

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

        private static IEnumerable<char> GetChars(
            char character
            )
        {
            switch (character)
            {
                case AllChars:
                    {
                        return new CharList(
                            Characters.DigitChars,
                            Characters.UpperAlphabetChars,
                            Characters.LowerAlphabetChars);
                    }
                case AllDigitChars:
                    {
                        return new CharList(
                            Characters.DigitChars);
                    }
                case AllAlphabetChars:
                    {
                        return new CharList(
                            Characters.UpperAlphabetChars,
                            Characters.LowerAlphabetChars);
                    }
                case AllUpperAlphabetChars:
                    {
                        return new CharList(
                            Characters.UpperAlphabetChars);
                    }
                case AllLowerAlphabetChars:
                    {
                        return new CharList(
                            Characters.LowerAlphabetChars);
                    }
                default:
                    {
                        if (CharIsValidValue(character))
                            return new CharList(character);

                        break;
                    }
            }

            return null;
        }

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

        private static IDictionary<char, int> CharsToDictionary(
            IEnumerable<char> characters
            )
        {
            if (characters != null)
            {
                IDictionary<char, int> result = new Dictionary<char, int>();

                foreach (char character in characters)
                {
                    if (result.ContainsKey(character))
                        result[character]++;
                    else
                        result.Add(character, 1);
                }

                return result;
            }

            return null;
        }

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

        private static IEnumerable<char> DictionaryToChars(
            IDictionary<char, int> dictionary,
            bool sort
            )
        {
            if (dictionary != null)
            {
                StringList keys = new StringList(dictionary.Keys);

                if (sort)
                    keys.Sort(); /* NOTE: O(N^2) is the worst case. */

                return keys.ToString(null, null, false);
            }

            return null;
        }

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

        private static IDictionary<char, int> Union(
            IDictionary<char, int> X,
            IDictionary<char, int> Y
            )
        {
            IDictionary<char, int> result = null;

            if ((X != null) || (Y != null))
            {
                result = new Dictionary<char, int>();

                if (X != null)
                {
                    foreach (KeyValuePair<char, int> pair in X)
                        result.Add(pair);

                    if (Y != null)
                    {
                        foreach (KeyValuePair<char, int> pair in Y)
                        {
                            if (result.ContainsKey(pair.Key))
                                result[pair.Key] += pair.Value;
                            else
                                result.Add(pair);
                        }
                    }
                }
                else
                {
                    foreach (KeyValuePair<char, int> pair in Y)
                        result.Add(pair);
                }
            }

            return result;
        }

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

        private static string Union(
            string X,
            string Y,
            bool sort
            )
        {
            return (string)DictionaryToChars(Union(CharsToDictionary(X),
                CharsToDictionary(Y)), sort);
        }

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

        #region Dead Code
#if DEAD_CODE
        private static IDictionary<char, int> Intersection(
            IDictionary<char, int> X,
            IDictionary<char, int> Y
            )
        {
            IDictionary<char, int> result = null;

            if ((X != null) && (Y != null))
            {
                result = new Dictionary<char, int>();

                foreach (KeyValuePair<char, int> pair in X)
                {
                    int value;

                    if (Y.TryGetValue(pair.Key, out value))
                        result.Add(pair.Key, pair.Value + value);
                }
            }

            return result;
        }

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

        private static string Intersection(
            string X,
            string Y,
            bool sort
            )
        {
            return (string)DictionaryToChars(Intersection(CharsToDictionary(X),
                CharsToDictionary(Y)), sort);
        }

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

        private static IDictionary<char, int> Difference(
            IDictionary<char, int> X,
            IDictionary<char, int> Y
            )
        {
            IDictionary<char, int> result = null;

            if (X != null)
            {
                result = new Dictionary<char, int>();

                foreach (KeyValuePair<char, int> pair in X)
                    if ((Y == null) || !Y.ContainsKey(pair.Key))
                        result.Add(pair);
            }

            return result;
        }

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

        private static string Difference(
            string X,
            string Y,
            bool sort
            )
        {
            return (string)DictionaryToChars(Difference(CharsToDictionary(X),
                CharsToDictionary(Y)), sort);
        }
#endif
        #endregion
        #endregion

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

        #region Public Methods
        public static IDictionary<long, string> Parse(
            string text,
            bool complex,
            bool space,
            bool sort,
            ref Result error
            )
        {
            if (text == null)
            {
                error = "invalid flags";
                return null;
            }

            StringBuilder name = StringOps.NewStringBuilder();
            StringBuilder value = StringOps.NewStringBuilder();

            IDictionary<long, string> result = new LongStringDictionary();

            bool haveName = false;
            bool open = false;
            long key = 0;

            for (int index = 0; index < text.Length; index++)
            {
                char character = text[index];

                switch (character)
                {
                    case Characters.OpenBrace:
                        {
                            if (!complex)
                            {
                                error = String.Format(
                                    "unexpected open brace at index {0}, simple-only mode",
                                    index);

                                return null;
                            }

                            if (open)
                            {
                                error = String.Format(
                                    "unexpected open brace at index {0}, already open",
                                    index);

                                return null;
                            }

                            //
                            // NOTE: This code cannot be reached.
                            //
                            //if (name.Length > 0)
                            //{
                            //    error = String.Format(
                            //        "unexpected name at index {0}",
                            //        index);
                            //
                            //    return null;
                            //}
                            //

                            if (value.Length > 0)
                            {
                                if (result.ContainsKey(NoKey))
                                    result[NoKey] = Union(result[NoKey], value.ToString(), sort);
                                else
                                    result.Add(NoKey, value.ToString());

                                value.Length = 0;
                            }

                            open = true;
                            break;
                        }
                    case Characters.CloseBrace:
                        {
                            if (!complex)
                            {
                                error = String.Format(
                                    "unexpected close brace at index {0}, simple-only mode",
                                    index);

                                return null;
                            }

                            if (!open)
                            {
                                error = String.Format(
                                    "unexpected close brace at index {0}, already closed",
                                    index);

                                return null;
                            }

                            if (!haveName)
                            {
                                error = String.Format(
                                    "unexpected close brace at index {0}, name incomplete",
                                    index);

                                return null;
                            }

                            if (name.Length == 0)
                            {
                                error = String.Format(
                                    "unexpected close brace at index {0}, name missing",
                                    index);

                                return null;
                            }

                            if (!HexadecimalNameToKey(name.ToString(), ref key))
                            {
                                error = String.Format(
                                    "invalid name {0}, must be a hexadecimal long integer",
                                    name);

                                return null;
                            }

                            if (result.ContainsKey(key))
                                result[key] = Union(result[key], value.ToString(), sort);
                            else
                                result.Add(key, value.ToString());

                            name.Length = 0;
                            value.Length = 0;

                            haveName = false;
                            open = false;
                            break;
                        }
                    default:
                        {
                            //
                            // NOTE: We could put this here; however, that would allow
                            //       spaces before, inside of, and after the name itself
                            //       and we do not want to be that liberal.
                            //
                            //if (space && Parser.IsWhiteSpace(character))
                            //    continue;

                            if (haveName || !open)
                            {
                                if (space && Parser.IsWhiteSpace(character))
                                    continue;

                                if (!CharIsValidValue(character))
                                {
                                    error = String.Format(
                                        "invalid value character '{0}' at index {1}",
                                        character, index);

                                    return null;
                                }

                                value.Append(character);
                            }
                            else
                            {
                                if (character == NameSepatator)
                                {
                                    haveName = true;
                                    continue;
                                }

                                if (!CharIsValidName(character))
                                {
                                    error = String.Format(
                                        "invalid name character '{0}' at index {1}",
                                        character, index);

                                    return null;
                                }

                                name.Append(character);

                                if (name.Length == NameLength)
                                    haveName = true;
                            }
                            break;
                        }
                }
            }

            if (complex && open)
            {
                error = "close brace expected";

                return null;
            }

            //
            // NOTE: This code cannot be reached.
            //
            //if (name.Length > 0)
            //{
            //    error = "unexpected name";
            //
            //    return null;
            //}
            //

            if (value.Length > 0)
            {
                if (result.ContainsKey(NoKey))
                    result[NoKey] = Union(result[NoKey], value.ToString(), sort);
                else
                    result.Add(NoKey, value.ToString());

                /* value.Length = 0; */
            }

            return result;
        }

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

        public static string Format(
            IDictionary<long, string> flags,
            bool legacy,
            bool compact,
            bool space,
            bool sort,
            ref Result error
            )
        {
            if (flags != null)
            {
                StringBuilder result = StringOps.NewStringBuilder();
                LongList keys = new LongList(flags.Keys);

                if (sort)
                    keys.Sort(); /* NOTE: O(N^2) is the worst case. */

                for (int index = 0; index < keys.Count; index++)
                {
                    long key = keys[index];

                    string keyFlags = compact ? (string)DictionaryToChars(
                        CharsToDictionary(flags[key]), sort) : flags[key];

                    if (key != NoKey)
                        result.AppendFormat(
                            legacy ? LegacyFlagFormat : FlagFormat,
                            key, keyFlags);
                    else
                        result.Append(keyFlags);

                    if (space && ((index + 1) < keys.Count))
                        result.Append(Characters.Space);
                }

                return result.ToString();
            }

            error = "invalid flags";
            return null;
        }

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

        public static bool Verify(
            string text,
            bool complex,
            bool space,
            ref Result error
            )
        {
            return (Parse(text, complex, space, false, ref error) != null);
        }

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

        public static bool Have(
            IDictionary<long, string> flags,
            long key,
            string haveFlags,
            bool all,
            bool strict
            )
        {
            if (flags != null)
            {
                if (haveFlags != null)
                {
                    if (flags.ContainsKey(key))
                    {
                        IDictionary<char, int> keyFlags =
                            CharsToDictionary(flags[key]);

                        if (keyFlags != null)
                        {
                            if (haveFlags.Length == 0)
                                return true; // have-none

                            for (int index = 0; index < haveFlags.Length; index++)
                            {
                                char character = haveFlags[index];

                                if (!CharIsValidValue(character))
                                {
                                    if (strict)
                                        return false; // fail
                                    else
                                        continue; // ignore
                                }

                                if (keyFlags.ContainsKey(character))
                                {
                                    if (!all)
                                        return true; // have-any

                                    continue; // have-all
                                }

                                if (all)
                                    return false; // not-have-all
                            }

                            return all; // have-all (?)
                        }
                    }
                    else if (haveFlags.Length == 0)
                    {
                        return true; // have-none
                    }
                }
                else
                {
                    return true;
                }
            }
            else if (haveFlags == null)
            {
                return true;
            }

            return false;
        }

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

        public static IDictionary<long, string> Change(
            IDictionary<long, string> flags,
            long key,
            string changeFlags,
            bool sort,
            ref Result error
            )
        {
            if (flags != null)
            {
                if (changeFlags != null)
                {
                    IDictionary<long, string> newFlags = new LongStringDictionary(flags);

                    IDictionary<char, int> keyFlags = newFlags.ContainsKey(key) ?
                        CharsToDictionary(newFlags[key]) : new Dictionary<char, int>();

                    bool add = true; /* NOTE: Default to add mode. */

                    for (int index = 0; index < changeFlags.Length; index++)
                    {
                        char character = changeFlags[index];

                        switch (character)
                        {
                            case AddCharacter:
                                {
                                    add = true;

                                    break;
                                }
                            case RemoveCharacter:
                                {
                                    add = false;

                                    break;
                                }
                            case SetCharacter:
                                {
                                    keyFlags.Clear();

                                    add = true;

                                    break;
                                }
                            case AllChars:
                            case AllDigitChars:
                            case AllAlphabetChars:
                            case AllUpperAlphabetChars:
                            case AllLowerAlphabetChars:
                                {
                                    IEnumerable<char> characters = GetChars(character);

                                    if (characters != null)
                                    {
                                        foreach (char newCharacter in characters)
                                        {
                                            if (add)
                                            {
                                                if (keyFlags.ContainsKey(newCharacter))
                                                    keyFlags[newCharacter]++;
                                                else
                                                    keyFlags.Add(newCharacter, 1);
                                            }
                                            else if (keyFlags.ContainsKey(newCharacter))
                                            {
                                                keyFlags.Remove(newCharacter);
                                            }
                                        }
                                    }
                                    else
                                    {
                                        error = String.Format(
                                            "invalid meta-character '{0}' at index {1}",
                                            character, index);

                                        return null;
                                    }
                                    break;
                                }
                            default:
                                {
                                    if (!CharIsValidValue(character))
                                    {
                                        error = String.Format(
                                            "invalid value character '{0}' at index {1}",
                                            character, index);

                                        return null;
                                    }

                                    if (add)
                                    {
                                        if (keyFlags.ContainsKey(character))
                                            keyFlags[character]++;
                                        else
                                            keyFlags.Add(character, 1);
                                    }
                                    else if (keyFlags.ContainsKey(character))
                                    {
                                        keyFlags.Remove(character);
                                    }
                                    break;
                                }
                        }
                    }

                    if (newFlags.ContainsKey(key))
                    {
                        if (keyFlags.Count > 0)
                            newFlags[key] = (string)DictionaryToChars(keyFlags, sort);
                        else
                            newFlags.Remove(key);
                    }
                    else if (keyFlags.Count > 0)
                    {
                        newFlags.Add(key, (string)DictionaryToChars(keyFlags, sort));
                    }

                    return newFlags;
                }
                else
                {
                    error = "invalid change flags";
                }
            }
            else
            {
                error = "invalid flags";
            }

            return null;
        }
        #endregion
    }
}
