public static NeurologyForm LoadNeurologyFormFrom(XDocument xmlDocument)
        {
            var formXml = xmlDocument.Root.Element("NeurologyForm");

            var neurologyForm = new NeurologyForm()
            {
                AnalContraction = GetBinaryObservationFrom(formXml.Element("AnalContraction")),
                AnalSensation = GetBinaryObservationFrom(formXml.Element("AnalSensation"))
            };

            neurologyForm.SetRightLowestNonKeyMuscleWithMotorFunction(formXml.Element("RightLowestNonKeyMuscleWithMotorFunction").Value);
            neurologyForm.SetLeftLowestNonKeyMuscleWithMotorFunction(formXml.Element("LeftLowestNonKeyMuscleWithMotorFunction").Value);

            foreach (var dermatomeElement in formXml.Descendants("Dermatome"))
                AddValuesFromDermatomeToForm(dermatomeElement, neurologyForm);

            return neurologyForm;
        }
 public NeurologyFormTotalsSummary Post(NeurologyFormModel formModel)
 {
     var neurologyForm = new NeurologyForm();
     formModel.BindTo(neurologyForm);
     return Algorithm.GetTotalsSummaryFor(neurologyForm);
 }
        public void BindTo(NeurologyForm neurologyForm)
        {
            neurologyForm.AnalContraction = AnalContraction;
            neurologyForm.AnalSensation = AnalSensation;

            if (!string.IsNullOrEmpty(RightLowestNonKeyMuscleWithMotorFunction))
                neurologyForm.SetRightLowestNonKeyMuscleWithMotorFunction(RightLowestNonKeyMuscleWithMotorFunction);

            if (!string.IsNullOrEmpty(LeftLowestNonKeyMuscleWithMotorFunction))
                neurologyForm.SetLeftLowestNonKeyMuscleWithMotorFunction(LeftLowestNonKeyMuscleWithMotorFunction);

            neurologyForm.UpdateLevelAt("C2", C2RightTouch, C2LeftTouch, C2RightPrick, C2LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("C3", C3RightTouch, C3LeftTouch, C3RightPrick, C3LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("C4", C4RightTouch, C4LeftTouch, C4RightPrick, C4LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("C5", C5RightTouch, C5LeftTouch, C5RightPrick, C5LeftPrick, C5RightMotor, C5LeftMotor);
            neurologyForm.UpdateLevelAt("C6", C6RightTouch, C6LeftTouch, C6RightPrick, C6LeftPrick, C6RightMotor, C6LeftMotor);
            neurologyForm.UpdateLevelAt("C7", C7RightTouch, C7LeftTouch, C7RightPrick, C7LeftPrick, C7RightMotor, C7LeftMotor);
            neurologyForm.UpdateLevelAt("C8", C8RightTouch, C8LeftTouch, C8RightPrick, C8LeftPrick, C8RightMotor, C8LeftMotor);
            neurologyForm.UpdateLevelAt("T1", T1RightTouch, T1LeftTouch, T1RightPrick, T1LeftPrick, T1RightMotor, T1LeftMotor);
            neurologyForm.UpdateLevelAt("T2", T2RightTouch, T2LeftTouch, T2RightPrick, T2LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T3", T3RightTouch, T3LeftTouch, T3RightPrick, T3LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T4", T4RightTouch, T4LeftTouch, T4RightPrick, T4LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T5", T5RightTouch, T5LeftTouch, T5RightPrick, T5LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T6", T6RightTouch, T6LeftTouch, T6RightPrick, T6LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T7", T7RightTouch, T7LeftTouch, T7RightPrick, T7LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T8", T8RightTouch, T8LeftTouch, T8RightPrick, T8LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T9", T9RightTouch, T9LeftTouch, T9RightPrick, T9LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T10", T10RightTouch, T10LeftTouch, T10RightPrick, T10LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T11", T11RightTouch, T11LeftTouch, T11RightPrick, T11LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("T12", T12RightTouch, T12LeftTouch, T12RightPrick, T12LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("L1", L1RightTouch, L1LeftTouch, L1RightPrick, L1LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("L2", L2RightTouch, L2LeftTouch, L2RightPrick, L2LeftPrick, L2RightMotor, L2LeftMotor);
            neurologyForm.UpdateLevelAt("L3", L3RightTouch, L3LeftTouch, L3RightPrick, L3LeftPrick, L3RightMotor, L3LeftMotor);
            neurologyForm.UpdateLevelAt("L4", L4RightTouch, L4LeftTouch, L4RightPrick, L4LeftPrick, L4RightMotor, L4LeftMotor);
            neurologyForm.UpdateLevelAt("L5", L5RightTouch, L5LeftTouch, L5RightPrick, L5LeftPrick, L5RightMotor, L5LeftMotor);
            neurologyForm.UpdateLevelAt("S1", S1RightTouch, S1LeftTouch, S1RightPrick, S1LeftPrick, S1RightMotor, S1LeftMotor);
            neurologyForm.UpdateLevelAt("S2", S2RightTouch, S2LeftTouch, S2RightPrick, S2LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("S3", S3RightTouch, S3LeftTouch, S3RightPrick, S3LeftPrick, "0", "0");
            neurologyForm.UpdateLevelAt("S4_5", S4_5RightTouch, S4_5LeftTouch, S4_5RightPrick, S4_5LeftPrick, "0", "0");
        }
        public void GetTotalsForNeurologyForm()
        {
            // Arrange
            var neurologyForm = new NeurologyForm()
            {
                AnalContraction = BinaryObservation.No,
                AnalSensation = BinaryObservation.Yes
            };

            neurologyForm.UpdateLevelAt("C2", "2", "2", "2", "2", "0", "0");
            neurologyForm.UpdateLevelAt("C3", "2", "2", "2", "2", "0", "0");
            neurologyForm.UpdateLevelAt("C4", "2", "2", "2", "2", "0", "0");
            neurologyForm.UpdateLevelAt("C5", "2", "2", "2", "2", "5", "5");
            neurologyForm.UpdateLevelAt("C6", "1", "1", "1", "0", "1", "1");
            neurologyForm.UpdateLevelAt("C7", "1", "0", "1", "0", "0", "0");
            neurologyForm.UpdateLevelAt("C8", "1", "0", "1", "0", "0", "0");
            neurologyForm.UpdateLevelAt("T1", "1", "0", "1", "0", "0", "0");
            neurologyForm.UpdateLevelAt("T2", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T3", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T4", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T5", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T6", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T7", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T8", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T9", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T10", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T11", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("T12", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("L1", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("L2", "NT", "NT", "NT", "NT", "NT", "NT");
            neurologyForm.UpdateLevelAt("L3", "NT", "NT", "NT", "NT", "NT", "NT");
            neurologyForm.UpdateLevelAt("L4", "NT", "NT", "NT", "NT", "NT", "NT");
            neurologyForm.UpdateLevelAt("L5", "NT", "NT", "NT", "NT", "NT", "NT");
            neurologyForm.UpdateLevelAt("S1", "NT", "NT", "NT", "NT", "NT", "NT");
            neurologyForm.UpdateLevelAt("S2", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("S3", "NT", "NT", "NT", "NT", "0", "0");
            neurologyForm.UpdateLevelAt("S4_5", "NT", "NT", "NT", "NT", "0", "0");

            // Act
            var summary = Algorithm.GetTotalsSummaryFor(neurologyForm);

            // Assert
            Assert.AreEqual("B,C,D", summary.AsiaImpairmentScale);
            Assert.AreEqual("I", summary.Completeness);
            Assert.AreEqual("UTD", summary.LeftLowerMotorTotal);
            Assert.AreEqual("C5", summary.LeftMotor);
            Assert.AreEqual("UTD", summary.LeftMotorTotal);
            Assert.AreEqual("NA", summary.LeftMotorZpp);
            Assert.AreEqual("UTD", summary.LeftPrickTotal);
            Assert.AreEqual("C5", summary.LeftSensory);
            Assert.AreEqual("NA", summary.LeftSensoryZpp);
            Assert.AreEqual("UTD", summary.LeftTouchTotal);
            Assert.AreEqual("6", summary.LeftUpperMotorTotal);
            Assert.AreEqual("UTD", summary.LowerMotorTotal);
            Assert.AreEqual("C5", summary.NeurologicalLevelOfInjury);
            Assert.AreEqual("UTD", summary.PrickTotal);
            Assert.AreEqual("UTD", summary.RightLowerMotorTotal);
            Assert.AreEqual("C5", summary.RightMotor);
            Assert.AreEqual("UTD", summary.RightMotorTotal);
            Assert.AreEqual("NA", summary.RightMotorZpp);
            Assert.AreEqual("UTD", summary.RightPrickTotal);
            Assert.AreEqual("C5", summary.RightSensory);
            Assert.AreEqual("NA", summary.RightSensoryZpp);
            Assert.AreEqual("UTD", summary.RightTouchTotal);
            Assert.AreEqual("6", summary.RightUpperMotorTotal);
            Assert.AreEqual("UTD", summary.TouchTotal);
            Assert.AreEqual("12", summary.UpperMotorTotal);
        }
        private static void AddValuesFromDermatomeToForm(XElement dermatomeElement, NeurologyForm neurologyForm)
        {
            // Right Touch
            var rightTouchName = dermatomeElement.Element("RightTouch").Value;
            var rightPrickName = dermatomeElement.Element("RightPrick").Value;
            var leftTouchName = dermatomeElement.Element("LeftTouch").Value;
            var leftPrickName = dermatomeElement.Element("LeftPrick").Value;

            var rightMotorElement = dermatomeElement.Element("RightMotor");
            var rightMotorName = rightMotorElement == null ? "0" : rightMotorElement.Value;

            var leftMotorElement = dermatomeElement.Element("LeftMotor");
            var leftMotorName = leftMotorElement == null ? "0" : leftMotorElement.Value;

            neurologyForm.UpdateLevelAt(dermatomeElement.Attribute("name").Value,
                    rightTouchName, leftTouchName,
                    rightPrickName, leftPrickName,
                    rightMotorName, leftMotorName);
        }
        /// <summary>
        /// Returns the results produced by the ISNCSCI Algorithm ir a raw values format.
        /// </summary>
        /// <param name="neurologyForm">Neurology form that has been populated with the values to be used in the algorithm calculations.</param>
        /// <returns>
        /// Totals in raw values format.  The results contain lists with every prossible value for each field.
        /// You can use the resulting object to obtained a summarized version, which uses ranges, by passing the result to the method GetTotalsSummaryFor
        /// </returns>
        public static NeurologyFormTotals GetTotalsFor(NeurologyForm neurologyForm)
        {
            var totals = new NeurologyFormTotals();

            UpdateTotalsWithLevelAt(neurologyForm, totals, neurologyForm.GetLevelWithName("C2"), false, false);

            totals.UpperMotorTotal = totals.RightUpperMotorTotal + totals.LeftUpperMotorTotal;
            totals.LowerMotorTotal = totals.RightLowerMotorTotal + totals.LeftLowerMotorTotal;
            totals.TouchTotal = totals.RightTouchTotal + totals.LeftTouchTotal;
            totals.PrickTotal = totals.RightPrickTotal + totals.LeftPrickTotal;

            var s4_5 = neurologyForm.GetLevelWithName("S4_5");

            var c1 = neurologyForm.GetLevelWithName("C2").Previous;

            if (totals.RightSensoryZppHasOnlySoftValues)
                totals.AddRightSensoryZppValue(c1);

            if (totals.LeftSensoryZppHasOnlySoftValues)
                totals.AddLeftSensoryZppValue(c1);

            if (totals.RightMotorZppHasOnlySoftValues)
                totals.AddRightMotorZppValue(c1);

            if (totals.LeftMotorZppHasOnlySoftValues)
                totals.AddLeftMotorZppValue(c1);

            if (totals.MostRostralRightLevelWithMotorFunction == null)
                totals.MostRostralRightLevelWithMotorFunction = c1;

            if (totals.MostRostralLeftLevelWithMotorFunction == null)
                totals.MostRostralLeftLevelWithMotorFunction = c1;

            if (totals.MostCaudalRightLevelWithMotorFunction == null)
                totals.MostCaudalRightLevelWithMotorFunction = c1;

            if (totals.MostCaudalLeftLevelWithMotorFunction == null)
                totals.MostCaudalLeftLevelWithMotorFunction = c1;

            // [ASIA learning center 2012-11-14] Sensory incomplete: Sacral sparing of sensory function
            var isSensoryIncomplete = neurologyForm.AnalSensation == BinaryObservation.Yes || neurologyForm.AnalSensation == BinaryObservation.NT
                || !"0".Equals(s4_5.RightTouchName) || !"0".Equals(s4_5.LeftTouchName)
                || !"0".Equals(s4_5.RightPrickName) || !"0".Equals(s4_5.LeftPrickName);

            var couldNotHaveMotorFunctionMoreThan3LevelsBelowMotorLevel = CouldNotHaveMotorFunctionMoreThan3LevelsBelowMotorLevel(neurologyForm, totals);

            var couldNotBeMotorIncomplete = (neurologyForm.AnalContraction == BinaryObservation.No || neurologyForm.AnalContraction == BinaryObservation.NT)
                && isSensoryIncomplete && couldNotHaveMotorFunctionMoreThan3LevelsBelowMotorLevel;

            if ((neurologyForm.AnalContraction == BinaryObservation.No || neurologyForm.AnalContraction == BinaryObservation.NT)
                && (neurologyForm.AnalSensation == BinaryObservation.No || neurologyForm.AnalSensation == BinaryObservation.NT)
                && s4_5.RightTouchValue == 0 && !s4_5.RightTouchImpairmentNotDueToSci
                && s4_5.RightPrickValue == 0 && !s4_5.RightPrickImpairmentNotDueToSci
                && s4_5.LeftTouchValue == 0 && !s4_5.LeftTouchImpairmentNotDueToSci
                && s4_5.LeftPrickValue == 0 && !s4_5.LeftPrickImpairmentNotDueToSci)
                totals.AddAsiaImpairmentScaleValue("A");

            if (couldNotBeMotorIncomplete && totals.MostRostralNeurologicalLevelOfInjury.Name != "S4_5")
                totals.AddAsiaImpairmentScaleValue("B");

            //// Not an ASIA E only
            //// AND not an ASIA A only
            if (totals.MostRostralNeurologicalLevelOfInjury.Name != "S4_5"
                && (isSensoryIncomplete || neurologyForm.AnalContraction == BinaryObservation.Yes || neurologyForm.AnalContraction == BinaryObservation.NT))
            {
                bool couldBeAsiaC;
                bool couldBeAsiaD;
                CouldBeAsiaCorD(neurologyForm, totals, out couldBeAsiaC, out couldBeAsiaD);

                if (couldBeAsiaC)
                    totals.AddAsiaImpairmentScaleValue("C");

                if (couldBeAsiaD)
                    totals.AddAsiaImpairmentScaleValue("D");
            }

            if (totals.RightSensoryContains("S4_5") && totals.LeftSensoryContains("S4_5")
                && totals.RightMotorContains("S4_5") && totals.LeftMotorContains("S4_5"))
                totals.AddAsiaImpairmentScaleValue("E");

            return totals;
        }
        /// <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>
        /// Evaluates the specified form and totals to determine if any of the different
        /// return conditions could produce a case where there could be motor function more
        /// than 3 levels below the injury level.
        /// </summary>
        /// <param name="neurologyForm">Form that was used to produce the totals.</param>
        /// <param name="totals">Totals retunred by the algorithm.</param>
        /// <returns>Flag indicating if any combination in the totals could have a case with motor function more than 3 levels below the injury level.</returns>
        private static bool CouldNotHaveMotorFunctionMoreThan3LevelsBelowMotorLevel(NeurologyForm neurologyForm, NeurologyFormTotals totals)
        {
            NeurologyFormLevel mostRostralRightLevelWithMotor = null;
            NeurologyFormLevel mostRostralLeftLevelWithMotor = null;

            var currentLevel = neurologyForm.GetLevelWithName("S1");

            while ((mostRostralRightLevelWithMotor == null || mostRostralLeftLevelWithMotor == null)
                && currentLevel != null // Could happen if SNL is C1
                && currentLevel.Ordinal >= totals.MostCaudalNeurologicalLevelOfInjury.Ordinal)
            {
                if (mostRostralRightLevelWithMotor == null &&
                    (currentLevel.RightMotorImpairmentNotDueToSci || currentLevel.HasOtherRightMotorFunction || (currentLevel.RightMotorValue != 0 && currentLevel.IsKeyMuscle)))
                    mostRostralRightLevelWithMotor = currentLevel;

                if (mostRostralLeftLevelWithMotor == null &&
                    (currentLevel.LeftMotorImpairmentNotDueToSci || currentLevel.HasOtherLeftMotorFunction || (currentLevel.LeftMotorValue != 0 && currentLevel.IsKeyMuscle)))
                    mostRostralLeftLevelWithMotor = currentLevel;

                currentLevel = currentLevel.Previous;
            }

            return (mostRostralRightLevelWithMotor == null || mostRostralRightLevelWithMotor.Ordinal - totals.MostCaudalRightMotor.Ordinal <= 3)
                && (mostRostralLeftLevelWithMotor == null || mostRostralLeftLevelWithMotor.Ordinal - totals.MostCaudalLeftMotor.Ordinal <= 3);
        }
        /// <summary>
        /// Evaluates the specified form and totals to determine if any of the different
        /// return conditions could produce a case where the Asia Impairment Scale is C o D.
        /// </summary>
        /// <param name="neurologyForm">Form that was used to produce the totals.</param>
        /// <param name="totals">Totals retunred by the algorithm.</param>
        /// <param name="couldBeAsiaC">out variable use as flag indicating if this case is a possible ASIA C.</param>
        /// <param name="couldBeAsiaD">out variable use as flag indicating if this case is a possible ASIA D.</param>
        private static void CouldBeAsiaCorD(NeurologyForm neurologyForm, NeurologyFormTotals totals, out bool couldBeAsiaC, out bool couldBeAsiaD)
        {
            couldBeAsiaC = false;
            couldBeAsiaD = false;
            NeurologyFormLevel rightMotor = null;
            NeurologyFormLevel leftMotor = null;

            // Check if the patient could be motor incoplete via sphincter contraction
            // Otherwise we need to check for motor function more than 3 levels below motor level.
            var couldHaveAnalContraction = neurologyForm.AnalContraction == BinaryObservation.Yes || neurologyForm.AnalContraction == BinaryObservation.NT;
            //var couldNotHaveAnalContraction = neurologyForm.AnalContraction == BinaryObservation.No || neurologyForm.AnalContraction == BinaryObservation.NT;

            var nliEnum = totals.GetNeurologicalLevelsOfInjury().GetEnumerator();

            while (nliEnum.MoveNext() && (!couldBeAsiaC || !couldBeAsiaD))
            {
                //var otherMotorIsMoreThanThreeLevelsBelowNli = (neurologyForm.)

                // RIGHT MOTOR
                // If not motor incomplete already, find the right motor level that correspond to this particular neurological level
                if (!couldHaveAnalContraction && (rightMotor == null || rightMotor.Ordinal < nliEnum.Current.Ordinal))
                {
                    var rightMotorValues = totals.GetRightMotorValues().GetEnumerator();
                    rightMotor = null;

                    while (rightMotor == null && rightMotorValues.MoveNext())
                    {
                        if (rightMotorValues.Current.Ordinal >= nliEnum.Current.Ordinal)
                            rightMotor = rightMotorValues.Current;
                    }

                    rightMotorValues.Dispose();
                }

                // LEFT MOTOR
                // If not motor incomplete already, find the left motor level that correspond to this particular neurological level
                if (!couldHaveAnalContraction && (leftMotor == null || leftMotor.Ordinal < nliEnum.Current.Ordinal))
                {
                    var leftMotorValues = totals.GetLeftMotorValues().GetEnumerator();
                    leftMotor = null;

                    while (leftMotor == null && leftMotorValues.MoveNext())
                    {
                        if (leftMotorValues.Current.Ordinal >= nliEnum.Current.Ordinal)
                            leftMotor = leftMotorValues.Current;
                    }

                    leftMotorValues.Dispose();
                }

                //if (couldNotHaveAnalContraction
                //    && (rightMotor == null || totals.MostCaudalRightLevelWithMotorFunction.Ordinal - rightMotor.Ordinal <= 3)
                //    && (leftMotor == null || totals.MostCaudalLeftLevelWithMotorFunction.Ordinal - leftMotor.Ordinal <= 3))
                //    couldBeAsiaB = true;

                // If the test is not motor incomplete at this level, do not continue to count motor levels, move to the next nli available.
                if (!couldHaveAnalContraction
                    && totals.MostCaudalRightLevelWithMotorFunction.Ordinal - rightMotor.Ordinal <= 3
                    && totals.MostCaudalLeftLevelWithMotorFunction.Ordinal - leftMotor.Ordinal <= 3)
                    continue;

                // When motor incomplete and the nli is S1 or more caudal, it is automatically D since there are no myotomes to count from.
                // We can break the loop.
                if (nliEnum.Current.Ordinal > 24) // Greater than L5 (24)
                {
                    couldBeAsiaD = true;
                    break;
                }

                // If motor incomplete, count the motor levels with muscle grade greater than two
                var levelsWithMuscleGradeGreaterThanTwo = 0;
                var levelsWithMuscleGradeLessThanThree = 0;
                var eligibleLevelCount = 0;
                var currentLevel = nliEnum.Current.Next;

                while (currentLevel != null)
                {
                    if (currentLevel.IsKeyMuscle)
                    {
                        eligibleLevelCount += 2;

                        if (currentLevel.RightMotorValue > 2 || currentLevel.RightMotorImpairmentNotDueToSci || NtRegex.IsMatch(currentLevel.RightMotorName))
                            levelsWithMuscleGradeGreaterThanTwo++;

                        if ((currentLevel.RightMotorValue < 3 || NtRegex.IsMatch(currentLevel.RightMotorName)) && !currentLevel.RightMotorImpairmentNotDueToSci)
                            levelsWithMuscleGradeLessThanThree++;

                        if (currentLevel.LeftMotorValue > 2 || currentLevel.LeftMotorImpairmentNotDueToSci || NtRegex.IsMatch(currentLevel.LeftMotorName))
                            levelsWithMuscleGradeGreaterThanTwo++;

                        if ((currentLevel.LeftMotorValue < 3 || NtRegex.IsMatch(currentLevel.LeftMotorName)) && !currentLevel.LeftMotorImpairmentNotDueToSci)
                            levelsWithMuscleGradeLessThanThree++;
                    }

                    currentLevel = currentLevel.Next;
                }

                // If not more than half the myotomes contain values less to 3, this is an Asia C
                if (levelsWithMuscleGradeLessThanThree > eligibleLevelCount / 2)
                    couldBeAsiaC = true;

                // If at least half the myotomes below the current NLI containing values greater or equal to 3, hooray! it is ASIA D.
                if (levelsWithMuscleGradeGreaterThanTwo >= eligibleLevelCount / 2)
                    couldBeAsiaD = true;
            }

            nliEnum.Dispose();
        }
 /// <summary>
 /// Produces a summarized version of a NeurologyFormTotals object which can be used to be displayed in an interface or be stored in a database.
 /// The values in the summary return will have ranges instead of lists of values.
 /// </summary>
 /// <param name="neurologyForm">Neurology form that has been populated with the values to be used in the algorithm calculations.</param>
 /// <returns>
 /// Summarized version of the totals which presents the results as ranges, rather than lists containing every possible value for every field.
 /// </returns>
 public static NeurologyFormTotalsSummary GetTotalsSummaryFor(NeurologyForm neurologyForm)
 {
     return GetTotalsSummaryFor(GetTotalsFor(neurologyForm));
 }