/// <summary>
        ///     This method finds chemical interaction bonds between atoms on separate chains.
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <param name="pdbFilename">The filename of the PDB file to parse for chemical interactions.</param>
        /// <param name="pdbIdChainIdList"></param>
        /// <param name="breakWhenFirstInteractionFound"></param>
        /// <param name="totalThreads"></param>
        /// <returns>Returns a list of atom pairs which are close enough in distance to have chemical interactions.</returns>
        public static List <AtomPair> FindInteractions(CancellationToken cancellationToken, decimal maxAtomInterationDistance /*= 8.0m*/, string pdbFilename, Dictionary <string, List <string> > pdbIdChainIdList, bool breakWhenFirstInteractionFound = false, int totalThreads = -1, bool sort = true, int requiredChains = -1)
        {
            if (ParameterValidation.IsLoadFilenameInvalid(pdbFilename)) // && ParameterValidation.IsProteinChainListContainerNullOrEmpty(pdbFileChains))
            {
                throw new ArgumentOutOfRangeException(nameof(pdbFilename));
            }

            string proteinId = ProteinDataBankFileOperations.PdbIdFromPdbFilename(pdbFilename);

            bool useCache = false;

            if (useCache && !string.IsNullOrWhiteSpace(proteinId))
            {
                var cachedInteractions = InteractionsCache.LoadPdbInteractionCache(proteinId, requiredChains);

                if (cachedInteractions != null)
                {
                    return(cachedInteractions);
                }
            }

            var chainIdList = pdbIdChainIdList != null ? (proteinId != null && pdbIdChainIdList.ContainsKey(proteinId) ? pdbIdChainIdList[proteinId].ToArray() : null) : null;

            ProteinChainListContainer proteinFileChains = ProteinDataBankFileOperations.PdbAtomicChains(pdbFilename, chainIdList, requiredChains, requiredChains, true);

            List <AtomPair> atomPairList = FindInteractions(cancellationToken, maxAtomInterationDistance, proteinId, pdbIdChainIdList, proteinFileChains, breakWhenFirstInteractionFound, totalThreads, sort, requiredChains);

            if (atomPairList == null)
            {
                // only save if null, otherwise, already saved in other method
                atomPairList = new List <AtomPair>();
                if (useCache)
                {
                    InteractionsCache.SavePdbInteractionCache(proteinId, atomPairList, requiredChains);
                }
            }

            return(atomPairList);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <param name="pdbFilename"></param>
        /// <param name="pdbIdChainIdList"></param>
        /// <param name="pdbFileChains"></param>
        /// <param name="singularAaToAaInteractions"></param>
        /// <param name="fullClusteringResult"></param>
        /// <returns></returns>
        public static ProteinInterfaceAnalysisResultData AnalyseProteinInterfaces(
            CancellationToken cancellationToken,
            decimal maxAtomInterationDistance,
            decimal minimumProteinInterfaceDensity,
            string pdbFilename,
            Dictionary <string, List <string> > pdbIdChainIdList,
            ProteinChainListContainer pdbFileChains,
            ProteinChainListContainer singularAaToAaInteractions,
            ClusteringFullResultListContainer fullClusteringResult)
        {
            if (ParameterValidation.IsLoadFilenameInvalid(pdbFilename))
            {
                throw new ArgumentOutOfRangeException(nameof(pdbFilename));
            }

            if (ParameterValidation.IsProteinChainListContainerNullOrEmpty(singularAaToAaInteractions))
            {
                throw new ArgumentOutOfRangeException(nameof(singularAaToAaInteractions));
            }

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

            string proteinId = ProteinDataBankFileOperations.PdbIdFromPdbFilename(pdbFilename);

            List <List <int> > chainStageProteinInterfaceCount;

            // Find how many proteinInterfaces at each stage.
            ClusteringFullResultListContainer proteinInterfacesClusteringResult = DetectProteinInterfaces(proteinId, singularAaToAaInteractions, fullClusteringResult, out chainStageProteinInterfaceCount, ClusteringProteinInterfaceDensityDetectionOptions.ResidueSequenceIndex, minimumProteinInterfaceDensity);

            // Find the last stage having required number of proteinInterfaces.
            int[] detectedBestClusterStagesIndexes = ProteinInterfaceTreeOptimalStageDetection.FindFinalProteinInterfaceStageIndexes(singularAaToAaInteractions, fullClusteringResult, proteinInterfacesClusteringResult, chainStageProteinInterfaceCount);

            int totalChains = singularAaToAaInteractions.ChainList.Count;

            var interactionProteinInterfaceClusteringHierarchyDataList = new List <InteractionProteinInterfaceClusteringHierarchyData>();

            int[] numberProteinInterfacesPerChain = FindNumberProteinInterfacesPerChain(proteinInterfacesClusteringResult, detectedBestClusterStagesIndexes);

            for (int chainIndex = 0; chainIndex < totalChains; chainIndex++)
            {
                int stageIndex = detectedBestClusterStagesIndexes[chainIndex];

                string chainIdLetter = SpreadsheetFileHandler.AlphabetLetterRollOver(chainIndex);

                var interactionProteinInterfaceClusteringHierarchyData = new InteractionProteinInterfaceClusteringHierarchyData(proteinId, chainIdLetter, numberProteinInterfacesPerChain[chainIndex], stageIndex + 1, fullClusteringResult.ChainList[chainIndex].StageList.Count);

                interactionProteinInterfaceClusteringHierarchyDataList.Add(interactionProteinInterfaceClusteringHierarchyData);
            }

            InteractionBetweenProteinInterfacesListContainer interactionBetweenProteinInterfacesContainer = CrossProteinInterfaceInteractions.FindInteractionsBetweenAnyProteinInterfaces(cancellationToken, maxAtomInterationDistance, pdbFilename, pdbIdChainIdList, pdbFileChains, singularAaToAaInteractions, fullClusteringResult, proteinInterfacesClusteringResult, detectedBestClusterStagesIndexes);

            List <ProteinInterfaceSequenceAndPositionData> analyseProteinInterfacesSequenceAndPositionData = AnalyseProteinInterfacesSequenceAndPositionData(pdbFilename, pdbIdChainIdList, pdbFileChains, singularAaToAaInteractions, proteinInterfacesClusteringResult, detectedBestClusterStagesIndexes, interactionBetweenProteinInterfacesContainer);

            var result = new ProteinInterfaceAnalysisResultData(
                detectedBestClusterStagesIndexes,
                proteinInterfacesClusteringResult,
                interactionProteinInterfaceClusteringHierarchyDataList,
                interactionBetweenProteinInterfacesContainer,
                analyseProteinInterfacesSequenceAndPositionData
                );

            return(result);
        }