public static int[] FindFinalProteinInterfaceStageIndexes(
            ProteinChainListContainer chainInteractingAtomLists,
            ClusteringFullResultListContainer clusters,
            ClusteringFullResultListContainer proteinInterfaces,
            List <List <int> > chainStageProteinInterfaceCount)
        {
            if (ParameterValidation.IsProteinChainListContainerNullOrEmpty(chainInteractingAtomLists))
            {
                throw new ArgumentOutOfRangeException(nameof(chainInteractingAtomLists));
            }

            if (ParameterValidation.IsClusteringFullResultListContainerNullOrEmpty(clusters))
            {
                throw new ArgumentOutOfRangeException(nameof(clusters));
            }

            if (ParameterValidation.IsClusteringFullResultListContainerNullOrEmpty(proteinInterfaces))
            {
                throw new ArgumentOutOfRangeException(nameof(proteinInterfaces));
            }

            if (ParameterValidation.IsListNullOrEmpty(chainStageProteinInterfaceCount))
            {
                throw new ArgumentOutOfRangeException(nameof(chainInteractingAtomLists));
            }

            int totalChains = chainInteractingAtomLists.ChainList.Count;
            var result      = new int[totalChains];

            for (int chainIndex = 0; chainIndex < totalChains; chainIndex++)
            {
                result[chainIndex] = -1;

                // Find the Max proteinInterfaces in proteinInterfaces list
                int chainMaxProteinInterfaceCount          = chainStageProteinInterfaceCount[chainIndex].Max();
                int chainMaxProteinInterfaceCountLastIndex = chainStageProteinInterfaceCount[chainIndex].LastIndexOf(chainMaxProteinInterfaceCount);

                for (int stageIndex = chainMaxProteinInterfaceCountLastIndex + 1; stageIndex < proteinInterfaces.ChainList[chainIndex].StageList.Count; stageIndex++)
                {
                    ClusteringFullResultListContainer.Chain.Stage proteinInterfacesLastStage = proteinInterfaces.ChainList[chainIndex].StageList[stageIndex - 1];
                    ClusteringFullResultListContainer.Chain.Stage proteinInterfacesThisStage = proteinInterfaces.ChainList[chainIndex].StageList[stageIndex];

                    // Check proteinInterfaces in proteinInterfacesThisStage contains the member of proteinInterfaces in proteinInterfacesLastStage
                    foreach (ClusteringFullResultListContainer.Chain.Stage.Cluster proteinInterfaceLastStage in proteinInterfacesLastStage.ClusterList)
                    {
                        foreach (int lastProteinInterfaceMember in proteinInterfaceLastStage.AtomIndexList)
                        {
                            bool lastProteinInterfaceMemberFound = false;

                            foreach (ClusteringFullResultListContainer.Chain.Stage.Cluster proteinInterfaceThisStage in proteinInterfacesThisStage.ClusterList)
                            {
                                if (proteinInterfaceThisStage.AtomIndexList.Contains(lastProteinInterfaceMember))
                                {
                                    lastProteinInterfaceMemberFound = true;
                                    break;
                                }
                            }

                            if (!lastProteinInterfaceMemberFound)
                            {
                                result[chainIndex] = stageIndex - 1;
                                break;
                            }
                        }

                        if (result[chainIndex] == stageIndex - 1)
                        {
                            break;
                        }
                    }

                    if (result[chainIndex] == stageIndex - 1)
                    {
                        break;
                    }
                }

                if (result[chainIndex] == -1)
                {
                    result[chainIndex] = chainMaxProteinInterfaceCountLastIndex;
                }
            }

            return(result);
        }
        /// <summary>
        ///     Removes groups from the cluster which do not meet the proteinInterface definition criteria.  Also provides a count of proteinInterfaces per
        ///     stage.
        /// </summary>
        /// <param name="proteinId"></param>
        /// <param name="chainInteractingAtomLists"></param>
        /// <param name="fullClusteringResult"></param>
        /// <param name="chainStageProteinInterfaceCount"></param>
        /// <param name="clusteringProteinInterfaceDensityDetectionOptions"></param>
        /// <param name="minimumProteinInterfaceAlphas"></param>
        /// <param name="maximumProteinInterfaceAlphas"></param>
        /// <param name="minimumProteinInterfaceDensity"></param>
        /// <param name="maximumProteinInterfaceDensity"></param>
        /// <returns></returns>
        public static ClusteringFullResultListContainer DetectProteinInterfaces(
            string proteinId,
            ProteinChainListContainer chainInteractingAtomLists,
            ClusteringFullResultListContainer fullClusteringResult,
            out List <List <int> > chainStageProteinInterfaceCount,
            ClusteringProteinInterfaceDensityDetectionOptions clusteringProteinInterfaceDensityDetectionOptions,// = ClusteringProteinInterfaceDensityDetectionOptions.ResidueSequenceIndex,
            decimal minimumProteinInterfaceDensity,
            decimal maximumProteinInterfaceDensity = -1,
            int minimumProteinInterfaceAlphas      = 4,
            int maximumProteinInterfaceAlphas      = -1
            )
        {
            if (ParameterValidation.IsProteinChainListContainerNullOrEmpty(chainInteractingAtomLists))
            {
                throw new ArgumentOutOfRangeException(nameof(chainInteractingAtomLists));
            }

            if (ParameterValidation.IsClusteringFullResultListContainerNullOrEmpty(fullClusteringResult))
            {
                throw new ArgumentOutOfRangeException(nameof(fullClusteringResult));
            }


            //var resultChainsStagesGroupsMembers = new List<List<List<List<int>>>>();
            var resultChainsStagesGroupsMembers = new ClusteringFullResultListContainer();

            chainStageProteinInterfaceCount = new List <List <int> >();

            for (int chainIndex = 0; chainIndex < fullClusteringResult.ChainList.Count; chainIndex++)
            {
                //var thisChain = new List<List<List<int>>>();

                var thisChain = new ClusteringFullResultListContainer.Chain();
                resultChainsStagesGroupsMembers.ChainList.Add(thisChain);

                var chainProteinInterfacesCount = new List <int>();
                chainStageProteinInterfaceCount.Add(chainProteinInterfacesCount);

                // Check every stage to see which has the lowest and highest number of suspected interaction proteinInterfaces.
                for (int stageIndex = 0; stageIndex < fullClusteringResult.ChainList[chainIndex].StageList.Count; stageIndex++)
                {
                    //var thisStage = new List<List<int>>();
                    var thisStage = new ClusteringFullResultListContainer.Chain.Stage();
                    thisChain.StageList.Add(thisStage);

                    int proteinInterfacesAtThisStage = 0;

                    // For every group, check if they have enough members (carbon alpha atoms) to meet the requirements of being an interaction proteinInterface.
                    for (int groupIndex = 0; groupIndex < fullClusteringResult.ChainList[chainIndex].StageList[stageIndex].ClusterList.Count; groupIndex++)
                    {
                        //var thisGroup = new List<int>();
                        var thisGroup = new ClusteringFullResultListContainer.Chain.Stage.Cluster();
                        thisStage.ClusterList.Add(thisGroup);

                        int groupTotalCarbonAlphas = fullClusteringResult.ChainList[chainIndex].StageList[stageIndex].ClusterList[groupIndex].AtomIndexList.Count;

                        if (((minimumProteinInterfaceAlphas > -1) && (groupTotalCarbonAlphas < minimumProteinInterfaceAlphas)) || ((maximumProteinInterfaceAlphas > -1) && groupTotalCarbonAlphas > maximumProteinInterfaceAlphas))
                        {
                            continue;
                        }

                        //var interactingGroup = clusters.ChainList[chainIndex].StageList[stageIndex].ClusterList[groupIndex].Select(member => chainInteractingAtomLists.ChainList[chainIndex].AtomList[member]).ToList();
                        var interactingGroup = new ProteinAtomListContainer
                        {
                            AtomList = fullClusteringResult.ChainList[chainIndex].StageList[stageIndex].ClusterList[groupIndex].AtomIndexList.Select(member => chainInteractingAtomLists.ChainList[chainIndex].AtomList[member]).ToList()
                        };

                        decimal totalProteinInterfaceLength;

                        switch (clusteringProteinInterfaceDensityDetectionOptions)
                        {
                        case ClusteringProteinInterfaceDensityDetectionOptions.ResidueSequenceIndex:
                            totalProteinInterfaceLength = Clustering.FindProteinInterfaceDistanceFromResidueSequenceIndex(interactingGroup);
                            break;

                        case ClusteringProteinInterfaceDensityDetectionOptions.ShortestRoutePath:
                            decimal bestPathDistance;
                            Clustering.BruteForceTravellingSalesmanProblemSolver(interactingGroup, out bestPathDistance);
                            totalProteinInterfaceLength = bestPathDistance;
                            break;

                        case ClusteringProteinInterfaceDensityDetectionOptions.LongestDistanceBetweenInteractions:
                            decimal longestDistance;
                            Clustering.FindLongestDistanceBetweenNodes(interactingGroup, out longestDistance);
                            totalProteinInterfaceLength = longestDistance;
                            break;

                        default:
                            throw new ArgumentOutOfRangeException();
                        }

                        // The length is the maximum distance between any two values/points or the total distance between them.
                        decimal interactionProteinInterfaceDensity = Math.Round((groupTotalCarbonAlphas / totalProteinInterfaceLength) * 100, 0);

                        if (((minimumProteinInterfaceDensity <= -1) || (interactionProteinInterfaceDensity >= minimumProteinInterfaceDensity)) && ((maximumProteinInterfaceDensity <= -1) || (interactionProteinInterfaceDensity <= maximumProteinInterfaceDensity)))
                        {
                            proteinInterfacesAtThisStage++;
                            //stageTotalProteinInterfaces[chainIndex].StageList[stageIndex]++;
                            for (int memberIndex = 0; memberIndex < fullClusteringResult.ChainList[chainIndex].StageList[stageIndex].ClusterList[groupIndex].AtomIndexList.Count; memberIndex++)
                            {
                                int thisMember = fullClusteringResult.ChainList[chainIndex].StageList[stageIndex].ClusterList[groupIndex].AtomIndexList[memberIndex];
                                thisGroup.AtomIndexList.Add(thisMember);
                            }
                            if (proteinInterfacesAtThisStage >= 4)
                            {
                                ////////Console.WriteLine(proteinInterfacesAtThisStage);
                            }
                        }
                    }

                    chainProteinInterfacesCount.Add(proteinInterfacesAtThisStage);
                }
            }

            return(resultChainsStagesGroupsMembers);
        }