public NeurologyForm()
        {
            C1 = new NeurologyFormLevel
            {
                Ordinal = 0,
                IsKeyMuscle = false,
                IsLowerMuscle = false,
                Name = "C1",
                Previous = null,
                RightMotorName = "5",
                RightMotorValue = 5,
                LeftMotorName = "5",
                LeftMotorValue = 5,
                RightTouchName = "2",
                RightTouchValue = 2,
                LeftTouchName = "2",
                LeftTouchValue = 2,
                RightPrickName = "2",
                RightPrickValue = 2,
                LeftPrickName = "2",
                LeftPrickValue = 2
            };

            _levels = new Dictionary<string, NeurologyFormLevel>();

            var previousLevel = C1;

            for (var i = 0; i < LevelNames.Length; i++)
            {
                var name = LevelNames[i];

                var currentLevel = new NeurologyFormLevel
                {
                    IsKeyMuscle = KeyMuscles.Contains(name),
                    IsLowerMuscle = (i >= 20 && i <= 24),
                    Name = name,
                    Ordinal = i + 1,
                    Previous = previousLevel
                };

                previousLevel.Next = currentLevel;
                previousLevel = currentLevel;
                _levels.Add(name, currentLevel);
            }
        }
        public void AddRightSensoryZppValue(NeurologyFormLevel level)
        {
            if (_rightSensoryZppValues.Any(l => l.Name.ToUpper() == level.Name)
                || "S4_5".Equals(level.Name))
                return;

            _rightSensoryZppValues.Add(level);
        }
        public void AddRightMotorValue(NeurologyFormLevel level)
        {
            if (_rightMotorValues.Any(l => l.Name.ToUpper() == level.Name))
                return;

            if (MostRostralRightMotor == null || level.Ordinal < MostRostralRightMotor.Ordinal)
                MostRostralRightMotor = level;

            if (MostCaudalRightMotor == null || level.Ordinal > MostCaudalRightMotor.Ordinal)
                MostCaudalRightMotor = level;

            _rightMotorValues.Add(level);
        }
        public void AddNeurologicalLevelOfInjury(NeurologyFormLevel level)
        {
            if (_neurologicalLevelsOfInjury.Any(l => l.Name.ToLower() == level.Name.ToLower()))
                return;

            if (MostRostralNeurologicalLevelOfInjury == null || level.Ordinal < MostRostralNeurologicalLevelOfInjury.Ordinal)
                MostRostralNeurologicalLevelOfInjury = level;

            if (MostCaudalNeurologicalLevelOfInjury == null || level.Ordinal > MostCaudalNeurologicalLevelOfInjury.Ordinal)
                MostCaudalNeurologicalLevelOfInjury = level;

            _neurologicalLevelsOfInjury.Add(level);
        }
        public void AddLeftSensoryValue(NeurologyFormLevel level)
        {
            if (_leftSensoryValues.Any(l => l.Name.ToUpper() == level.Name))
                return;

            _leftSensoryValues.Add(level);
        }
        public void AddLeftMotorZppValue(NeurologyFormLevel level)
        {
            if (_leftMotorZppValues.Any(l => l.Name.ToUpper() == level.Name)
                || "S4_5".Equals(level.Name))
                return;

            _leftMotorZppValues.Add(level);
        }
        private static void UpdateLevel(NeurologyFormLevel level, string rightTouchName, string leftTouchName,
            string rightPrickName, string leftPrickName, string rightMotorName, string leftMotorName)
        {
            // Right Touch
            level.RightTouchName = rightTouchName;
            level.RightTouchValue = GetDermatomeValue(rightTouchName, NormalSensoryValue);
            level.RightTouchImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(rightTouchName);

            // Left Touch
            level.LeftTouchName = leftTouchName;
            level.LeftTouchValue = GetDermatomeValue(leftTouchName, NormalSensoryValue);
            level.LeftTouchImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(leftTouchName);

            // Right Prick
            level.RightPrickName = rightPrickName;
            level.RightPrickValue = GetDermatomeValue(rightPrickName, NormalSensoryValue);
            level.RightPrickImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(rightPrickName);

            // Left Prick
            level.LeftPrickName = leftPrickName;
            level.LeftPrickValue = GetDermatomeValue(leftPrickName, NormalSensoryValue);
            level.LeftPrickImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(leftPrickName);

            // Right Motor
            level.RightMotorName = rightMotorName;
            level.RightMotorValue = GetDermatomeValue(rightMotorName, NormalMotorValue);
            level.RightMotorImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(rightMotorName);

            // Left Motor
            level.LeftMotorName = leftMotorName;
            level.LeftMotorValue = GetDermatomeValue(leftMotorName, NormalMotorValue);
            level.LeftMotorImpairmentNotDueToSci = NonSciImpairmentRegex.IsMatch(leftMotorName);

            if (!level.IsKeyMuscle)
            {
                if ((level.RightTouchValue == 2 || level.RightTouchImpairmentNotDueToSci)
                    && (level.RightPrickValue == 2 || level.RightPrickImpairmentNotDueToSci))
                {
                    level.RightMotorName = "5";
                    level.RightMotorValue = 5;
                }
                else
                {
                    level.RightMotorName = (NtRegex.IsMatch(level.RightTouchName) || level.RightTouchValue == 2)
                        && (NtRegex.IsMatch(level.RightPrickName) || level.RightPrickValue == 2) ? "NT" : "0";
                    level.RightMotorValue = 0;
                }

                if ((level.LeftTouchValue == 2 || level.LeftTouchImpairmentNotDueToSci)
                    && (level.LeftPrickValue == 2 || level.LeftPrickImpairmentNotDueToSci))
                {
                    level.LeftMotorName = "5";
                    level.LeftMotorValue = 5;
                }
                else
                {
                    level.LeftMotorName = (NtRegex.IsMatch(level.LeftTouchName) || level.LeftTouchValue == 2)
                        && (NtRegex.IsMatch(level.LeftPrickName) || level.LeftPrickValue == 2) ? "NT" : "0";
                    level.LeftMotorValue = 0;
                }
            }
        }
        public void SetRightLowestNonKeyMuscleWithMotorFunction(string levelName)
        {
            if (string.IsNullOrEmpty(levelName))
                return;

            var key = levelName.ToUpper();

            if (!_levels.ContainsKey(key))
                return;

            if (RightLowestNonKeyMuscleWithMotorFunction != null)
                RightLowestNonKeyMuscleWithMotorFunction.HasOtherRightMotorFunction = false;

            RightLowestNonKeyMuscleWithMotorFunction = _levels[key];
            RightLowestNonKeyMuscleWithMotorFunction.HasOtherRightMotorFunction = true;
        }
        /// <summary>
        /// Recursive method which iterates through the values in a nuerology form while it updates the totals generating the results produced by the algorithm.
        /// </summary>
        /// <param name="neurologyForm">Form being evaluated.</param>
        /// <param name="totals">Brand new totals object where the results are to be stored.</param>
        /// <param name="level">Current neurology level being evaluated</param>
        /// <param name="nextNonKeyMuscleShouldBeRightMotor">Flag used to evaluate the Kathy Collins condition on the right motor results.</param>
        /// <param name="nextNonKeyMuscleShouldBeLeftMotor">Flag used to evaluate the Kathy Collins condition on the left motor results.</param>
        private static void UpdateTotalsWithLevelAt(NeurologyForm neurologyForm, NeurologyFormTotals totals, NeurologyFormLevel level, bool nextNonKeyMuscleShouldBeRightMotor, bool nextNonKeyMuscleShouldBeLeftMotor)
        {
            totals.RightTouchTotal += level.RightTouchValue;
            totals.LeftTouchTotal += level.LeftTouchValue;
            totals.RightPrickTotal += level.RightPrickValue;
            totals.LeftPrickTotal += level.LeftPrickValue;

            if (level.IsKeyMuscle)
            {
                if (level.IsLowerMuscle)
                {
                    if (level.RightMotorImpairmentNotDueToSci && !totals.RightLowerMotorTotalHasImpairmentNotDueToSci)
                        totals.RightLowerMotorTotalHasImpairmentNotDueToSci = true;

                    if (level.LeftMotorImpairmentNotDueToSci && !totals.LeftLowerMotorTotalHasImpairmentNotDueToSci)
                        totals.LeftLowerMotorTotalHasImpairmentNotDueToSci = true;

                    if (NtRegex.IsMatch(level.RightMotorName) && !level.RightMotorImpairmentNotDueToSci)
                        totals.RightLowerMotorContainsNt = true;

                    if (NtRegex.IsMatch(level.LeftMotorName) && !level.LeftMotorImpairmentNotDueToSci)
                        totals.LeftLowerMotorContainsNt = true;
                }
                else
                {
                    if (level.RightMotorImpairmentNotDueToSci && !totals.RightUpperMotorTotalHasImpairmentNotDueToSci)
                        totals.RightUpperMotorTotalHasImpairmentNotDueToSci = true;

                    if (level.LeftMotorImpairmentNotDueToSci && !totals.LeftUpperMotorTotalHasImpairmentNotDueToSci)
                        totals.LeftUpperMotorTotalHasImpairmentNotDueToSci = true;

                    if (NtRegex.IsMatch(level.RightMotorName) && !level.RightMotorImpairmentNotDueToSci)
                        totals.RightUpperMotorContainsNt = true;

                    if (NtRegex.IsMatch(level.LeftMotorName) && !level.LeftMotorImpairmentNotDueToSci)
                        totals.LeftUpperMotorContainsNt = true;
                }
            }
            else
            {
                if (nextNonKeyMuscleShouldBeRightMotor)
                {
                    nextNonKeyMuscleShouldBeRightMotor = false;
                    totals.AddRightMotorValue(level.Previous);

                    if (!totals.RightSensoryHasOnlySoftValues)
                        totals.RightMotorHasOnlySoftValues = false;
                }

                if (nextNonKeyMuscleShouldBeLeftMotor)
                {
                    nextNonKeyMuscleShouldBeLeftMotor = false;
                    totals.AddLeftMotorValue(level.Previous);

                    if (!totals.LeftSensoryHasOnlySoftValues)
                        totals.LeftMotorHasOnlySoftValues = false;
                }
            }

            if (level.RightTouchImpairmentNotDueToSci && !totals.RightTouchTotalHasImpairmentNotDueToSci)
                totals.RightTouchTotalHasImpairmentNotDueToSci = true;

            if (level.LeftTouchImpairmentNotDueToSci && !totals.LeftTouchTotalHasImpairmentNotDueToSci)
                totals.LeftTouchTotalHasImpairmentNotDueToSci = true;

            if (level.RightPrickImpairmentNotDueToSci && !totals.RightPrickTotalHasImpairmentNotDueToSci)
                totals.RightPrickTotalHasImpairmentNotDueToSci = true;

            if (level.LeftPrickImpairmentNotDueToSci && !totals.LeftPrickTotalHasImpairmentNotDueToSci)
                totals.LeftPrickTotalHasImpairmentNotDueToSci = true;

            // Check if a column contains any NT value so we can properly format the total presented to the user
            if (NtRegex.IsMatch(level.RightTouchName) && !level.RightTouchImpairmentNotDueToSci && !totals.RightTouchContainsNt)
                totals.RightTouchContainsNt = true;

            if (NtRegex.IsMatch(level.LeftTouchName) && !level.LeftTouchImpairmentNotDueToSci && !totals.LeftTouchContainsNt)
                totals.LeftTouchContainsNt = true;

            if (NtRegex.IsMatch(level.RightPrickName) && !level.RightPrickImpairmentNotDueToSci && !totals.RightPrickContainsNt)
                totals.RightPrickContainsNt = true;

            if (NtRegex.IsMatch(level.LeftPrickName) && !level.LeftPrickImpairmentNotDueToSci && !totals.LeftPrickContainsNt)
                totals.LeftPrickContainsNt = true;

            if (totals.RightSensoryHasOnlySoftValues &&
                ((level.RightTouchValue != 2 && !level.RightTouchImpairmentNotDueToSci) ||
                 (level.RightPrickValue != 2 && !level.RightPrickImpairmentNotDueToSci)))
            {
                totals.AddRightSensoryValue(level.Previous);

                if ("S4_5".Equals(level.Name)
                    && (level.RightTouchValue == 2 || NtRegex.IsMatch(level.RightTouchName))
                    && (level.RightPrickValue == 2 || NtRegex.IsMatch(level.RightPrickName)))
                {
                    totals.AddRightSensoryValue(level);

                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                        totals.AddNeurologicalLevelOfInjury(level);
                }

                if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                    totals.AddNeurologicalLevelOfInjury(level.Previous);

                if ((level.RightTouchValue != 2 && !NtRegex.IsMatch(level.RightTouchName))
                    || (level.RightPrickValue != 2 && !NtRegex.IsMatch(level.RightPrickName)))
                {
                    totals.RightSensoryHasOnlySoftValues = false;
                    totals.NeurologicalLevelOfInjuryHasOnlySoftValues = false;
                }

                if (level.IsKeyMuscle)
                {
                    nextNonKeyMuscleShouldBeRightMotor = true;
                    totals.HasRightCollins = true;
                }
            }

            if (totals.LeftSensoryHasOnlySoftValues &&
                ((level.LeftTouchValue != 2 && !level.LeftTouchImpairmentNotDueToSci) ||
                 (level.LeftPrickValue != 2 && !level.LeftPrickImpairmentNotDueToSci)))
            {
                totals.AddLeftSensoryValue(level.Previous);

                if ("S4_5".Equals(level.Name)
                    && (level.LeftTouchValue == 2 || NtRegex.IsMatch(level.LeftTouchName))
                    && (level.LeftPrickValue == 2 || NtRegex.IsMatch(level.LeftPrickName)))
                {
                    totals.AddLeftSensoryValue(level);

                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                        totals.AddNeurologicalLevelOfInjury(level);
                }

                if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                    totals.AddNeurologicalLevelOfInjury(level.Previous);

                if ((level.LeftTouchValue != 2 && !NtRegex.IsMatch(level.LeftTouchName))
                    || (level.LeftPrickValue != 2 && !NtRegex.IsMatch(level.LeftPrickName)))
                {
                    totals.LeftSensoryHasOnlySoftValues = false;
                    totals.NeurologicalLevelOfInjuryHasOnlySoftValues = false;
                }

                if (level.IsKeyMuscle)
                {
                    nextNonKeyMuscleShouldBeLeftMotor = true;
                    totals.HasLeftCollins = true;
                }
            }

            if (totals.RightMotorHasOnlySoftValues && level.RightMotorValue != 5 && !level.RightMotorImpairmentNotDueToSci)
            {
                if (level.IsKeyMuscle && (level.RightMotorValue >= 3 || NtRegex.IsMatch(level.RightMotorName)))
                {
                    totals.AddRightMotorValue(level);

                    // Check if left won't make the NLI have to be the previous level.
                    // Let the logic for left motor handle the SNL instead
                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues
                        && (level.LeftMotorValue > 2 || level.LeftMotorImpairmentNotDueToSci || NtRegex.IsMatch(level.LeftMotorName)))
                    {
                        totals.AddNeurologicalLevelOfInjury(level);

                        if (!NtRegex.IsMatch(level.RightMotorName))
                            totals.NeurologicalLevelOfInjuryHasOnlySoftValues = false;
                    }
                }

                if (level.RightMotorValue < 3 || NtRegex.IsMatch(level.RightMotorName))
                {
                    totals.AddRightMotorValue(level.Previous);

                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                    {
                        totals.AddNeurologicalLevelOfInjury(level.Previous);

                        if (!NtRegex.IsMatch(level.RightMotorName))
                            totals.NeurologicalLevelOfInjuryHasOnlySoftValues = false;
                    }
                }

                if (!NtRegex.IsMatch(level.RightMotorName))
                    totals.RightMotorHasOnlySoftValues = false;
            }

            if (totals.LeftMotorHasOnlySoftValues && level.LeftMotorValue != 5 && !level.LeftMotorImpairmentNotDueToSci)
            {
                if (level.IsKeyMuscle && (level.LeftMotorValue >= 3 || NtRegex.IsMatch(level.LeftMotorName)))
                {
                    totals.AddLeftMotorValue(level);

                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                        totals.AddNeurologicalLevelOfInjury(level);
                }

                if (level.LeftMotorValue < 3 || NtRegex.IsMatch(level.LeftMotorName))
                {
                    totals.AddLeftMotorValue(level.Previous);

                    if (totals.NeurologicalLevelOfInjuryHasOnlySoftValues)
                        totals.AddNeurologicalLevelOfInjury(level.Previous);
                }

                if (!NtRegex.IsMatch(level.LeftMotorName))
                {
                    totals.LeftMotorHasOnlySoftValues = false;
                    totals.NeurologicalLevelOfInjuryHasOnlySoftValues = false;
                }
            }

            /* -- RECURSIVE CALL --------------- */
            if (level.Next != null)
                UpdateTotalsWithLevelAt(neurologyForm, totals, level.Next,
                    nextNonKeyMuscleShouldBeRightMotor && totals.RightMotorHasOnlySoftValues,
                    nextNonKeyMuscleShouldBeLeftMotor && totals.LeftMotorHasOnlySoftValues);

            #region This happens when there are INTACT values -------------------------------------------
            if ("S4_5".Equals(level.Name))
            {
                if (totals.RightSensoryHasOnlySoftValues && totals.LeftSensoryHasOnlySoftValues
                && totals.RightMotorHasOnlySoftValues && totals.LeftMotorHasOnlySoftValues)
                    totals.AddNeurologicalLevelOfInjury(level);

                if (totals.RightSensoryHasOnlySoftValues)
                {
                    totals.AddRightSensoryValue(level);
                    totals.RightSensoryHasOnlySoftValues = false;
                }

                if (totals.LeftSensoryHasOnlySoftValues)
                {
                    totals.AddLeftSensoryValue(level);
                    totals.LeftSensoryHasOnlySoftValues = false;
                }

                if (totals.RightMotorHasOnlySoftValues)
                {
                    totals.AddRightMotorValue(level);
                    totals.RightMotorHasOnlySoftValues = false;
                }

                if (totals.LeftMotorHasOnlySoftValues)
                {
                    totals.AddLeftMotorValue(level);
                    totals.LeftMotorHasOnlySoftValues = false;
                }
            }
            #endregion

            if (totals.RightSensoryZppHasOnlySoftValues && (!"0".Equals(level.RightTouchName) || !"0".Equals(level.RightPrickName)))
            {
                if ((level.RightTouchValue > 0 || level.RightTouchImpairmentNotDueToSci)
                    || (level.RightPrickValue > 0 || level.RightPrickImpairmentNotDueToSci))
                    totals.RightSensoryZppHasOnlySoftValues = false;

                totals.AddRightSensoryZppValue(level);
            }

            if (totals.LeftSensoryZppHasOnlySoftValues && (!"0".Equals(level.LeftTouchName) || !"0".Equals(level.LeftPrickName)))
            {
                if ((level.LeftTouchValue > 0 || level.LeftTouchImpairmentNotDueToSci)
                    || (level.LeftPrickValue > 0 || level.LeftPrickImpairmentNotDueToSci))
                    totals.LeftSensoryZppHasOnlySoftValues = false;

                totals.AddLeftSensoryZppValue(level);
            }

            if (totals.RightMotorZppHasOnlySoftValues
                && (level.HasOtherRightMotorFunction
                    || (!"0".Equals(level.RightMotorName) && (level.IsKeyMuscle || totals.RightMotorContains(level.Name)))))
            {
                if ((level.RightMotorImpairmentNotDueToSci || level.HasOtherRightMotorFunction || !NtRegex.IsMatch(level.RightMotorName)) &&
                    (level.IsKeyMuscle
                    || level.Ordinal < 4
                    || (level.Ordinal > 25 && !totals.RightUpperMotorContainsNt && !totals.RightLowerMotorContainsNt && !totals.HasRightCollins)
                    || (level.Ordinal > 8 && level.Ordinal < 21 && !totals.RightUpperMotorContainsNt)))
                    totals.RightMotorZppHasOnlySoftValues = false;

                totals.AddRightMotorZppValue(level);
            }

            if (totals.LeftMotorZppHasOnlySoftValues
                && (level.HasOtherLeftMotorFunction
                    || (!"0".Equals(level.LeftMotorName) && (level.IsKeyMuscle || totals.LeftMotorContains(level.Name)))))
            {
                if ((level.LeftMotorImpairmentNotDueToSci || level.HasOtherLeftMotorFunction || !NtRegex.IsMatch(level.LeftMotorName)) &&
                    (level.IsKeyMuscle
                    || level.Ordinal < 4
                    || (level.Ordinal > 25 && !totals.LeftUpperMotorContainsNt && !totals.LeftLowerMotorContainsNt && !totals.HasLeftCollins)
                    || (level.Ordinal > 8 && level.Ordinal < 21 && !totals.LeftUpperMotorContainsNt)))
                    totals.LeftMotorZppHasOnlySoftValues = false;

                totals.AddLeftMotorZppValue(level);
            }

            // Update most Rostral levels with motor function
            if ((level.IsKeyMuscle || level.HasOtherRightMotorFunction)
                && totals.MostRostralRightLevelWithMotorFunction == null
                && (level.RightMotorImpairmentNotDueToSci || level.HasOtherRightMotorFunction || (level.RightMotorValue != 0 && level.IsKeyMuscle)))
                totals.MostRostralRightLevelWithMotorFunction = level;

            if ((level.IsKeyMuscle || level.HasOtherLeftMotorFunction)
                && totals.MostRostralLeftLevelWithMotorFunction == null
                && (level.LeftMotorImpairmentNotDueToSci || level.HasOtherLeftMotorFunction || (level.LeftMotorValue != 0 && level.IsKeyMuscle)))
                totals.MostRostralLeftLevelWithMotorFunction = level;

            // Update most Caudal levels with motor function
            if ((level.IsKeyMuscle || level.HasOtherRightMotorFunction)
                && totals.MostCaudalRightLevelWithMotorFunction == null
                && (!"0".Equals(level.RightMotorName) || level.HasOtherRightMotorFunction))
                totals.MostCaudalRightLevelWithMotorFunction = level;

            if ((level.IsKeyMuscle || level.HasOtherLeftMotorFunction)
                && totals.MostCaudalLeftLevelWithMotorFunction == null
                && (!"0".Equals(level.LeftMotorName) || level.HasOtherLeftMotorFunction))
                totals.MostCaudalLeftLevelWithMotorFunction = level;

            if (!level.IsKeyMuscle)
                return;

            if (level.IsLowerMuscle)
            {
                totals.RightLowerMotorTotal += level.RightMotorValue;
                totals.LeftLowerMotorTotal += level.LeftMotorValue;
            }
            else
            {
                totals.RightUpperMotorTotal += level.RightMotorValue;
                totals.LeftUpperMotorTotal += level.LeftMotorValue;
            }
        }
        /// <summary>
        /// Compares two neurology levels based on property &quot;Ordinal&quot;.
        /// </summary>
        /// <param name="a">Neurology form level A</param>
        /// <param name="b">Neurology form level B</param>
        /// <returns>
        /// Returns
        /// &ndash; 1 if a is more caudal (the ordinal of a is greater than the one of b)
        /// &ndash; 0 if both ordinal values are the same
        /// &ndash; -1 if b is more caudal (the ordinal of b is greater than the one of a).
        /// </returns>
        private static int CompareNeurologyFormTotalsLevelByOrdinal(NeurologyFormLevel a,
                                                                    NeurologyFormLevel b)
        {
            // Both are null so they are equal
            // OR
            // a is null and b is not so b is greater
            if (a == null)
                return b == null ? 0 : -1;

            // b is null and a is not so a is greater
            if (b == null)
                return 1;

            return a.Ordinal > b.Ordinal ? 1 : a.Ordinal == b.Ordinal ? 0 : -1;
        }