Untitled

mail@pastecode.io avatar
unknown
csharp
7 months ago
38 kB
3
Indexable
Never
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using static Cleverence.CompactForms.MaskedEditText.Mask.Options;
using Characters = Cleverence.CompactForms.MaskedEditText.Mask.Options.Characters;


namespace Cleverence.CompactForms.MaskedEditText
{

#if __ANDROID__
    [DebuggerDisplay("{" + nameof(MaskString) + "}")]
#endif
    public sealed class Mask
    {
        #region Public Properties
        /// <summary>
        /// Characters for filling empty spaces in mask
        /// </summary>
        public char MaskEmptyChar
        {
            get
            {
                return _maskEmptyChar;
            }
            set
            {
                _maskEmptyChar = value;
            }
        }
        private char _maskEmptyChar = '_';

        /// <summary>
        /// Keyboard type used for this mask
        /// </summary>
        public KeyboardType KeyboardType
        {
            get
            {
                return _keyboardType;
            }
        }
        private KeyboardType _keyboardType = KeyboardType.None;
        #endregion

        #region Private Properties
        private readonly MaskNode _head;

        public string MaskString { get; private set; }


        private readonly Options.MaskOptions _maskOptions;
        #endregion

        #region Miscelanious
        /// <summary>
        /// Class that contains enums for operations with mask
        /// </summary>
        public class Options
        {

            /// <summary>
            /// Modes used for ValidateString function
            /// </summary>
            public enum Validation
            {
                /// <summary>
                /// Must match mask's length
                /// </summary>
                MatchLength,

                /// <summary>
                /// Can't exceed length of a mask, but can be less
                /// </summary>
                SmallerOrEqualLength,

                /// <summary>
                /// Can exceed length of a mask, but can't be smaller
                /// </summary>
                BiggerOrEqualLength,

                /// <summary>
                /// Can be bigger or smaller than mask's length
                /// </summary>
                FreeLength
            }

            /// <summary>
            /// Modes used for DecorateString function
            /// </summary>
            public enum DecorateOptions
            {
                /// <summary>
                /// Decorates and filling missing places with empty spaces
                /// </summary>
                DecorateEmpty,

                /// <summary>
                /// Stops once reaches end of string
                /// </summary>
                KeepEmpty
            }

            /// <summary>
            /// Modes used for special mask options
            /// </summary>
            [Flags]
            public enum MaskOptions
            {
                /// <summary>
                /// Default flag for initialization
                /// </summary>
                None = 0,

                /// <summary>
                /// Return empty string for ToString() in case if no data is entered
                /// </summary>
                HideMaskIfEmpty = 1 << 0,

                /// <summary>
                /// Replaces text on entering instead inserting
                /// </summary>
                ReplaceMode = 1 << 1,

                /// <summary>
                /// Removed characters are replaced with empty space instead of text being shifted left
                /// </summary>
                StaticMode = 1 << 2,

                /// <summary>
                /// Allows the mask to be expanded without limit
                /// </summary>
                Limitless = 1 << 3,

                /// <summary>
                /// Allows you to show an empty mask character for characters that do not have displayed characters
                /// </summary>
                DisplayMaskEmptyChar = 1 << 4,

                /// <summary>
                /// Allows you to ignore the position of the mask symbol when entering, which gives the effect of a "smart" mask that can determine whether the mask rules are satisfied with the current input
                /// </summary>
                IgnoreMaskCharPosition = 1 << 5,
            }

            /// <summary>
            /// Modes used for Count method
            /// </summary>
            [Flags]
            public enum Characters
            {
                None = 0,

                /// <summary>
                /// Decorators (hardcoded characters)
                /// </summary>
                Decorators = 1 << 0,

                /// <summary>
                /// Characters that users filled in
                /// </summary>
                Filled = 1 << 1,

                /// <summary>
                /// Characters that aren't filled by user
                /// </summary>
                Empty = 1 << 2,

                /// <summary>
                /// Overflowing characters (characters after the mask)
                /// </summary>
                Overflowing = 1 << 3,

                /// <summary>
                /// Overriden characters (returns overriden instead of empty)
                /// </summary>
                Overriden = 1 << 4,

                /// <summary>
                /// The characters that must be displayed
                /// </summary>
                ReqiredDisplayCharacter = 1 << 5,

                /// <summary>
                /// All characters
                /// </summary>
                All = Decorators | Filled | Empty | Overflowing | Overriden | ReqiredDisplayCharacter
            }
        }

        /// <summary>
        /// Convert mask into usable form from maskString
        /// </summary>
        /// <param name="maskString">String that contains mask</param>
        /// <param name="options">Additional mask options</param>
        public Mask(string maskString, INodeTypeProvider nodeTypeProvider, params Options.MaskOptions[] options)
        {
            MaskNode current = null;
            bool screening = false;
            List<char> debugString = new List<char>(maskString.Length);
            foreach (char character in maskString)
            {
                debugString.Add(character);

                if (character.Equals('\\'))
                {
                    screening = true;
                    continue;
                }

                NodeType info = new NodeType(null, KeyboardType.None, true);

                if (!screening)
                    info = nodeTypeProvider.GetCharInfo(character);
                else
                    screening = false;

                MaskNode node = new MaskNode()
                {
                    Character = character,
                    PreviousNode = current,
                    Regex = info.RegEx,
                    IsHardcoded = string.IsNullOrEmpty(info.RegEx),
                    IsReqiredDisplayCharacter = info.IsReqiredDisplayCharacter,
                };

                if (info.Type > KeyboardType)
                    _keyboardType = info.Type;

                _head = _head ?? node;
                if (current != null) current.NextNode = node;

                current = node;
            }

            _head = _head ?? new MaskNode();

            if (options.Length > 0)
            {
                foreach (Options.MaskOptions option in options)
                    _maskOptions |= option;
            }
            else
            {
                _maskOptions = 0;
            }

            MaskString = new string(debugString.ToArray());
        }
        #endregion

        #region Public methods

        /// <summary>
        /// Validate that testString matches this mask
        /// </summary>
        /// <param name="testString">String to test</param>
        /// <param name="mode">Validation mode</param>
        /// <param name="hasMask">If true - checks the message with the mask. Otherwise checks if the mask can be applied</param>
        /// <param name="allowEmpty">Include empty spaces in validation</param>
        /// <returns>True if string matches the mask</returns>
        public bool ValidateString(string testString, Options.Validation mode, bool hasMask, bool allowEmpty)
        {
            if (_head == null)
            {
                switch (mode)
                {
                    case Options.Validation.FreeLength:
                    case Options.Validation.SmallerOrEqualLength:
                    case Options.Validation.BiggerOrEqualLength:
                        return true;

                    case Options.Validation.MatchLength:
                        return false;
                }
            }

            bool result = true;
            if (testString.Length > 0)
                ForEachNode((n, c) =>
                {
                    Queue<char> characters = new Queue<char>(testString);

                    char testChar = characters.Dequeue();

                    if (n.IsHardcoded)
                    {
                        if (hasMask && testChar != n.Character)
                        {
                            c.Break();
                            result = false;
                        }
                    }
                    else
                    {
                        if (n.Regex != null && !Regex.Match(testChar.ToString(), n.Regex).Success)
                        {
                            if (!allowEmpty || !testChar.Equals(MaskEmptyChar))
                            {
                                c.Break();
                                result = false;
                            }
                        }
                    }
                }, null);

            if (!result) return false;

            if (!mode.Equals(Options.Validation.FreeLength))
            {
                switch (mode)
                {
                    case Options.Validation.SmallerOrEqualLength:
                        if (!(testString.Length <= Count(false)))
                            return false;
                        return true;
                    case Options.Validation.MatchLength:
                        if (testString.Length != Count(false))
                            return false;
                        return true;
                    case Options.Validation.BiggerOrEqualLength:
                        if (!(testString.Length >= Count(false))) return false;
                        return true;
                    default:
                        return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Decorates string with the mask, putting the control characters at correct places
        /// </summary>
        /// <param name="input">Input string to be decorated</param>
        /// <param name="mode">Decorating mode</param>
        /// <returns>Decorated string with mask characters or same input in case of validation failure</returns>
        public string DecorateString(string input, Options.DecorateOptions mode)
        {
            if (!ValidateString(input, Options.Validation.FreeLength, false, false)) return input;

            string finalString = "";
            bool fillEmptySpaces = mode.Equals(Options.DecorateOptions.DecorateEmpty);

            MaskNode current = _head;
            int inputStrIdx = 0;

            if (current == null) return input;

            do
            {
                char inputChar = MaskEmptyChar;

                if (inputStrIdx < input.Length) inputChar = input[inputStrIdx];
                else if (!fillEmptySpaces) break;

                if (current.IsHardcoded)
                {
                    finalString += current.Character;
                    continue;
                }

                finalString += inputChar;
                inputStrIdx++;
            } while ((current = current.NextNode) != null);

            return finalString;
        }

        /// <summary>
        /// Returns raw string without decorators
        /// </summary>
        /// <returns>String without decorations failure</returns>
        public string GetRaw()
        {
            string @string = "";

            ForEachNode((node, control) =>
            {
                if (!node.IsHardcoded && node.DisplayedCharacter != null)
                    @string += node.DisplayedCharacter;
            }, null);

            return @string;
        }

        /// <summary>
        /// Gets the closest best position to put cursor at, based on the mask
        /// </summary>
        /// <param name="position">Current position to start searching from</param>
        /// <param name="opposite">Go in backward direction</param>
        /// <param name="minSteps">Minimum steps to take before stopping getting valid position</param>
        /// <param name="allowAfterTail">Allow null character after tail to be included in GetValidSelection</param>
        /// <returns>Best closest position for editing</returns>
        public int GetValidSelection(int position, bool opposite, int minSteps, bool allowAfterTail)
        {
            MaskNode current = _head;
            int validPos = position;

            for (int i = 0; i < position; i++)
            {
                if (current.NextNode == null)
                {
                    position = allowAfterTail ? i + 1 : i;
                    break;
                }

                current = current.NextNode;
            }

            if (!current.IsHardcoded)
            {
                if (minSteps == 0)
                    return position;

                minSteps--;
            }

            if (opposite && current.PreviousNode != null)
            {
                while ((current = current.PreviousNode) != null)
                {
                    if (position > 0) position--;
                    if (!current.IsHardcoded && (minSteps == 0 || current.PreviousNode == null))
                    {
                        validPos = position;
                        break;
                    }

                    if (minSteps > 0) minSteps--;
                }
            }
            else if (opposite == false && current.NextNode != null)
            {
                while ((current = current.NextNode) != null)
                {
                    position++;
                    if (!current.IsHardcoded && (minSteps == 0 || current.NextNode == null))
                    {
                        validPos = position;
                        break;
                    }

                    if (minSteps > 0) minSteps--;
                }

                if (minSteps > 0 && allowAfterTail && current == null) validPos = position + 1;
            }
            else
            {
                if (current != null && current.NextNode == null && allowAfterTail)
                    validPos = position + 1;
            }

            return Math.Max(0, Math.Min(Count(true), validPos));
        }

        /// <summary>
        /// Fixes selection to return the position between hardcoded characters
        /// </summary>
        /// <param name="pos">Position of cursor</param>
        /// <returns>Valid position of cursor</returns>
        public int FixSelection(int pos)
        {
            MaskNode targetNode = GetNode(pos,false);

            if (targetNode == null) return GetValidSelection(pos,false,0,true);
            if (targetNode.IsHardcoded) {
                if(targetNode.PreviousNode != null && !targetNode.PreviousNode.IsHardcoded) return pos;
            }

            return GetValidSelection(pos,false,0,true);
        }

        /// <summary>
        /// Converts mask with data into string form
        /// </summary>
        /// <param name="showEmptyMask">Show empty mask</param>
        /// <param name="characters">Characters which to display</param>
        /// <returns>Filled string with data</returns>
        public string ToString(bool? showEmptyMask, params Characters[] characters)
        {
            showEmptyMask = showEmptyMask ?? (_maskOptions & Options.MaskOptions.HideMaskIfEmpty) == 0;

            Characters options = Characters.None;
            foreach (var character in characters) options |= character;

            string @string = "";

            if (!showEmptyMask.Value && Count(Characters.Filled) == 0)
                return string.Empty;

            ForEachNode(n => ProcessNode(n, options, ref @string), null);

            return @string;
        }

        char? GetCharacter(char? @char) {
            return @char == '\0' ? null : @char;
        }

        void ProcessNode(MaskNode current, Characters options, ref string @string)
        {
            char? @char = null;

                if (current.DisplayedCharacter == null && options.HasFlag(Characters.Empty))
                {
                    if (current.OverrideCharacter != null && options.HasFlag(Characters.Overriden))
                        @char = current.OverrideCharacter;
                    else if (current.IsHardcoded && options.HasFlag(Characters.Decorators))
                        @char = current.Character;
                    else if (_maskOptions.HasFlag(Options.MaskOptions.DisplayMaskEmptyChar))
                        @char = MaskEmptyChar;
                    else
                        @char = ' ';
            }
                else if (current.DisplayedCharacter != null)
                {
                    if ((options.HasFlag(Characters.Filled) && !current.IsTemporary)
                        || (options.HasFlag(Characters.Overflowing) && current.IsTemporary))
                        @char = current.DisplayedCharacter;
                }

            @string += GetCharacter(@char);
        }

        public string ToString(params Characters[] characters)
        {
            return ToString(null, characters);
        }

        /// <summary>
        /// Converts mask with data into string form
        /// </summary>
        /// <returns>Filled string with data</returns>
        public override string ToString()
        {
            return ToString(Characters.All);
        }

        /// <summary>
        /// Inserts string starting at start position
        /// </summary>
        /// <param name="string">String to insert</param>
        /// <param name="start">Starting position</param>
        /// <param name="shift">New cursor position</param>
        /// <param name="createNewNodes">Create new nodes if needed</param>
        public void Insert(string @string, int start, out int shift, bool? createNewNodes)
        {
            createNewNodes = createNewNodes ?? _maskOptions.HasFlag(Options.MaskOptions.Limitless);

            if (_maskOptions.HasFlag(Options.MaskOptions.ReplaceMode))
            {
                InternalInsert(@string, start, out shift, createNewNodes.Value);
            }
            else if (_maskOptions.HasFlag(Options.MaskOptions.IgnoreMaskCharPosition))
            {
                InternalInsert2(@string, start, out shift, createNewNodes.Value);
            }
            else
            {
                string firstHalf = "";
                string secondHalf = "";
                int idx = 0;
                shift = 0;

                ForEachNode(n =>
                {
                    if (idx < start && !n.IsHardcoded && n.DisplayedCharacter != null)
                        firstHalf += n.DisplayedCharacter;
                    else if (idx >= start && !n.IsHardcoded && n.DisplayedCharacter != null)
                        secondHalf += n.DisplayedCharacter;

                    idx++;
                }, null);

                int _;
                InternalRemove(0, Count(true), out _);
                InternalInsert(firstHalf + @string, 0, out shift, createNewNodes.Value);
                InternalInsert(secondHalf, shift, out _, createNewNodes.Value);
            }
        }

        /// <summary>
        /// Inserts string starting at start position
        /// </summary>
        /// <param name="string">String to insert</param>
        /// <param name="start">Starting position</param>
        /// <param name="createNewNodes">Create new nodes if needed</param>
        public void Insert(string @string, int start, bool? createNewNodes)
        {
            int _;
            Insert(@string, start, out _, createNewNodes);
        }

        /// <summary>
        /// Removes certain amount of characters from mask filled data
        /// </summary>
        /// <param name="start">Starting index</param>
        /// <param name="count">Amount of characters to remove</param>
        /// <param name="shift">New cursor position</param>
        public void Remove(int start, int count, out int shift)
        {
            InternalRemove(start, count, out shift);

            if (_maskOptions.HasFlag(Options.MaskOptions.StaticMode)) return;

            string data = GetRaw();

            int _;
            InternalRemove(0, Count(true), out _);
            InternalInsert(data, 0, out _, true);
        }

        /// <summary>
        /// Removes certain amount of characters from mask filled data
        /// </summary>
        /// <param name="start">Starting index</param>
        /// <param name="count">Amount of characters to remove</param>
        public void Remove(int start, int count)
        {
            int _;
            Remove(start, count, out _);
        }

        /// <summary>
        /// Get size of the mask
        /// </summary>
        /// <param name="countTemporary">Include temporary (overflowing) symbols</param>
        /// <returns>Size of the mask</returns>
        public int Count(bool countTemporary)
        {
            Characters options = Characters.Filled | Characters.Decorators | Characters.Empty;
            options |= countTemporary ? Characters.Overflowing : 0;

            return Count(options);
        }

        /// <summary>
        /// Get size of the data in mask by parameters
        /// </summary>
        /// <param name="options">Types of characters to count</param>
        /// <returns></returns>
        public int Count(Characters options)
        {
            int count = 0;

            ForEachNode((n, c) => {
                if ((options.HasFlag(Characters.Empty) && n.DisplayedCharacter == null && !n.IsHardcoded)
                    || (options.HasFlag(Characters.Decorators) && n.IsHardcoded)
                    || (options.HasFlag(Characters.Overflowing) && n.IsTemporary)
                    || (options.HasFlag(Characters.Filled) && n.DisplayedCharacter != null)
                    || (options.HasFlag(Characters.ReqiredDisplayCharacter) && !n.IsHardcoded && n.IsReqiredDisplayCharacter))

                    count++;
            }, null);

            return count;
        }

        /// <summary>
        /// Makes mask display specific characters that are passed in newMask
        /// </summary>
        /// <param name="newMask">New mask to display. Empty will clear overrides</param>
        public void OverrideMask(string newMask)
        {
            if (newMask.Length != Count(false)) return;

            Queue<char> characters = new Queue<char>(newMask);

            bool clearMask = characters.Count == 0;

            ForEachNode(n => {
                if (clearMask) n.OverrideCharacter = null;
                else n.OverrideCharacter = characters.Dequeue();
            }, null);
        }

        /// <summary>
        /// Function to get the latest filled position. If it's decorator - then gives you position at the end of the decorators
        /// </summary>
        /// <returns>Latest filled position</returns>
        public int GetMaxFilledLength()
        {
            int count = 0;
            int lastDisplayedSymbolIdx = 0;
            MaskNode node = null;
            ForEachNode(n => {
                if (n.DisplayedCharacter != null)
                {
                    lastDisplayedSymbolIdx = count;
                    node = n;
                }

                count++;
            }, null);

            if ((node != null && node.NextNode != null && node.NextNode.IsHardcoded) || (node != null && node.IsHardcoded) || (_head != null && _head.IsHardcoded))
            {
                int idx = GetValidSelection(lastDisplayedSymbolIdx + 1, false, 0, true);
                MaskNode potentialNode = GetNode(idx, false);

                if (potentialNode != null && potentialNode.IsHardcoded && potentialNode.NextNode == null)
                    return lastDisplayedSymbolIdx + 1;

                return idx;
            }

            if (Count(Characters.Filled) == 0)
                return lastDisplayedSymbolIdx;
            else
                return lastDisplayedSymbolIdx + 1;
        }

        //public Android.Text.InputTypes GetKeyboardInputType(Android.Text.InputTypes inputType) => KeyboardType switch
        //{
        //    KeyboardType.Numeric when inputType == Android.Text.InputTypes.ClassText => Android.Text.InputTypes.ClassNumber,
        //    KeyboardType.Alphanumeric when inputType == Android.Text.InputTypes.ClassText => Android.Text.InputTypes.TextVariationFilter,
        //    _ => inputType
        //};

        public void ForEachNode(Action<MaskNode> action, MaskNode startingNode)
        {
            ForEachNode((n, _) => action.Invoke(n), startingNode);
        }

        public void ForEachNode(Action<MaskNode, LoopControl> action, MaskNode startingNode)
        {
            MaskNode current = startingNode ?? _head;
            LoopControl control = new LoopControl();

            if (current == null) return;

            do
            {
                action.Invoke(current, control);

                if (control.ProcessBreak()) break;
            } while ((current = current.NextNode) != null);
        }

        #endregion

        #region Private Methods
        private void InternalRemove(int start, int count, out int shift)
        {
            MaskNode startingNode = GetNode(start,false);
            shift = 0;

            if (startingNode == null) return;

            ForEachNode((node, control) => {
                if (count == 0)
                {
                    control.Break();
                    return;
                }

                if (!node.IsHardcoded)
                {
                    if (node.IsTemporary)
                    {
                        node.PreviousNode.NextNode = node.NextNode;
                        if (node.NextNode != null)
                            node.NextNode.PreviousNode = node.PreviousNode;
                    }
                    else
                    {
                        node.DisplayedCharacter = null;
                    }
                }

                count--;
            }, startingNode);

            if (startingNode.IsHardcoded)
                shift = GetValidSelection(start, true,0,true) + 1;
            else if (startingNode.PreviousNode != null && startingNode.PreviousNode.IsHardcoded)
                shift = GetValidSelection(start - 1, true,0,true) + 1;
            else
                shift = GetValidSelection(start, true,0,true);
        }

        private void InternalInsert(string @string, int start, out int shift, bool createNewNodes)
        {
            MaskNode startingNode = GetNode(start, createNewNodes);
            Queue<char> characters = new Queue<char>(@string);
            bool validForm = ValidateString(@string, Options.Validation.BiggerOrEqualLength, true, false);
            shift = 0;

            if (startingNode == null)
            {
                shift = start;
                return;
            }

            int nodeIdx = 0;
            int newCursorPos = start;

            ForEachNode((node, control) => {
                if (characters.Count == 0)
                {
                    control.Break();
                    return;
                }

                if (!node.IsHardcoded)
                {
                    char @char = characters.Dequeue();

                    if (node.Regex != null && Regex.Match(new string(@char, 1), node.Regex).Success || node.IsTemporary)
                    {
                        node.DisplayedCharacter = @char;
                        newCursorPos = nodeIdx + 1;
                    }
                    else if (!_maskOptions.HasFlag(Options.MaskOptions.StaticMode))
                    {
                        do
                        {
                            if ((node.Regex == null || !Regex.Match(new string(@char, 1), node.Regex).Success) &&
                                !node.IsTemporary) continue;

                            node.DisplayedCharacter = @char;
                            newCursorPos = nodeIdx + 1;
                            break;
                        } while (characters.TryDequeue(out @char));
                    }
                }
                else if (validForm)
                {
                    characters.Dequeue();
                }

                if (characters.Count > 0 && createNewNodes && node.NextNode == null)
                    node.NextNode = new MaskNode
                    {
                        IsTemporary = true,
                        PreviousNode = node
                    };

                // ReSharper disable once AccessToModifiedClosure
                nodeIdx++;
            }, startingNode);

            //if (newCursorPos != start) newCursorPos++;

            shift = GetValidSelection(newCursorPos,false,0,true);
        }

        private void InternalInsert2(string @string, int start, out int shift, bool createNewNodes)
        {
            MaskNode startingNode = GetNode(start, createNewNodes);
            Queue<char> characters = new Queue<char>(@string);
            bool validForm = ValidateString(@string, Options.Validation.BiggerOrEqualLength, true, false);
            shift = 0;

            if (startingNode == null)
            {
                shift = start;
                return;
            }

            int nodeIdx = 0;
            int newCursorPos = start;
            var insertedString = new StringBuilder();
            ForEachNode((node, control) => {
                if (characters.Count == 0)
                {
                    control.Break();
                    return;
                }

                if (!node.IsHardcoded) 
                { 
                    char @char = characters.Dequeue();

                    if (node.IsReqiredDisplayCharacter && node.Regex != null && Regex.Match(new string(@char, 1), node.Regex).Success || node.IsTemporary)
                    {
                        node.DisplayedCharacter = @char;
                    }
                    else
                    {
                        ForEachNode((internalNode, internalLoop) =>
                        {
                            if (internalNode.IsReqiredDisplayCharacter && internalNode.DisplayedCharacter == null  && node.Regex != null && Regex.Match(new string(@char, 1), node.Regex).Success || node.IsTemporary)
                            {
                                internalNode.DisplayedCharacter = @char;
                                internalLoop.Break();
                            }
                            else if (internalNode.IsReqiredDisplayCharacter && internalNode.DisplayedCharacter == null)
                            {
                                internalLoop.Break();
                            }
                        }, node.NextNode);
                    }

                    //if (node.Regex != null && Regex.Match(new string(@char, 1), node.Regex).Success || node.IsTemporary)
                    //{
                    //    node.DisplayedCharacter = @char;
                    //    newCursorPos = nodeIdx + 1;
                    //}
                    //ForEachNode((internalNode, internalLoop) => 
                    //{
                    //    if(internalNode.IsReqiredDisplayCharacter)
                    //    {
                    //        internalNode.DisplayedCharacter = @char;
                    //    }
                    //}, node);
                }
                else if (validForm)
                {
                    characters.Dequeue();
                }

                if (characters.Count > 0 && createNewNodes && node.NextNode == null)
                    node.NextNode = new MaskNode
                    {
                        IsTemporary = true,
                        PreviousNode = node
                    };

                // ReSharper disable once AccessToModifiedClosure
                nodeIdx++;
            }, startingNode);

            //if (newCursorPos != start) newCursorPos++;

            shift = GetValidSelection(newCursorPos, false, 0, true);
        }

        private MaskNode GetNode(int index, bool createIfNotFound)
        {
            MaskNode current = _head;

            for (int i = 0; i < index; i++)
            {
                if (current.NextNode == null)
                {
                    if (createIfNotFound)
                    {
                        MaskNode node = new MaskNode()
                        {
                            IsTemporary = true,
                            PreviousNode = current
                        };

                        current.NextNode = node;

                        return node;
                    }

                    return null;
                }

                current = current.NextNode;
            }

            return current;
        }
        #endregion
    }

    #region Classes/Enums/Extensions
    public sealed class MaskNode
    {
        private MaskNode _previousNode;
        private MaskNode _nextNode;
        private string _regex = ".";
        private char _character;
        private char? _displayedCharacter;
        private char? _overrideCharacter;
        private bool _isHardcoded;
        private bool _isTemporary;
        private bool _isReqiredDisplayCharacter;

        internal MaskNode PreviousNode
        {
            get { return _previousNode; }
            set { _previousNode = value; }
        }

        internal MaskNode NextNode
        {
            get { return _nextNode; }
            set { _nextNode = value; }
        }

        internal string Regex
        {
            get { return _regex; }
            set { _regex = value; }
        }

        internal char Character
        {
            get { return _character; }
            set { _character = value; }
        }

        internal char? DisplayedCharacter
        {
            get { return _displayedCharacter; }
            set { _displayedCharacter = value; }
        }

        internal char? OverrideCharacter
        {
            get { return _overrideCharacter; }
            set { _overrideCharacter = value; }
        }

        internal bool IsHardcoded
        {
            get { return _isHardcoded; }
            set { _isHardcoded = value; }
        }

        internal bool IsTemporary
        {
            get { return _isTemporary; }
            set { _isTemporary = value; }
        }

        /// <summary>
        /// Требуется отображать символ?
        /// </summary>
        internal bool IsReqiredDisplayCharacter
        {
            get { return _isReqiredDisplayCharacter; }
            set { _isReqiredDisplayCharacter = value; }
        }
    }

    public sealed class LoopControl
    {
        private bool _willBreak;

        internal void Break()
        {
            _willBreak = true;
        }

        internal bool ProcessBreak()
        {
            bool result = _willBreak;
            _willBreak = false;
            return result;
        }
    }

    public sealed class NodeType
    {
        internal string RegEx { get; private set; }
        internal KeyboardType Type { get; private set; }

        /// <summary>
        /// Требуется ли отображать значение
        /// </summary>
        public bool IsReqiredDisplayCharacter { get; private set; }

        public NodeType(string regEx, KeyboardType type, bool isReqiredDisplayCharacter)
        {
            RegEx = regEx;
            Type = type;
            IsReqiredDisplayCharacter = isReqiredDisplayCharacter;
        }
    }

    public enum KeyboardType
    {
        None,
        Numeric,
        Alphanumeric
    }

    internal static class Extensions
    {
        internal static bool TryDequeue<T>(this Queue<T> obj, out T item)
        {
            item = default(T);
            if (obj.Count == 0) return false;
            item = obj.Dequeue();
            return true;
        }

        internal static bool HasFlag(this Enum item1, Enum item2) {
            return (Convert.ToInt32(item1) & Convert.ToInt32(item2)) != 0;
        }
    }
    #endregion
}
Leave a Comment