public static double VectorDistanceValue(VectorDistanceMeasurementValues vectorDistanceMeasurementValues, bool[] isInteraction1, bool[] isInteraction2)
        {
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }
            if (isInteraction1 == null)
            {
                throw new ArgumentNullException(nameof(isInteraction1));
            }
            if (isInteraction2 == null)
            {
                throw new ArgumentNullException(nameof(isInteraction2));
            }

            if (isInteraction1.Length != isInteraction2.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(isInteraction1), "Interaction vectors must be the same size");
            }

            double distance = 0;

            for (var index = 0; index < isInteraction1.Length; index++)
            {
                distance += VectorDistanceValue(vectorDistanceMeasurementValues, isInteraction1[index], isInteraction2[index]);
            }

            return(distance);
        }
        public static void CustomDistance(bool[] array1, bool[] array2, VectorDistanceMeasurementValues vectorDistanceMeasurementValues, out double optimisticDistance /*, out double pessimisticDistance*/, int offsetStep = 1)
        {
            if (array1 == null)
            {
                throw new ArgumentNullException(nameof(array1));
            }
            if (array2 == null)
            {
                throw new ArgumentNullException(nameof(array2));
            }
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }

            var lengthDifference = Math.Abs(array1.Length - array2.Length);

            optimisticDistance = double.MaxValue;
            //pessimisticDistance = double.MinValue;

            for (var index = 0; index <= lengthDifference; index += offsetStep)
            {
                double neutralOffsetDistance = CustomDistanceOffset(array1, array2, vectorDistanceMeasurementValues, index);

                if (neutralOffsetDistance < optimisticDistance)
                {
                    optimisticDistance = neutralOffsetDistance;
                }

                //if (neutralOffsetDistance > pessimisticDistance)
                //{
                //    pessimisticDistance = neutralOffsetDistance;
                //}
            }
        }
        public static double VectorDistanceValue(VectorDistanceMeasurementValues vectorDistanceMeasurementValues, bool isInteraction1, bool isInteraction2)
        {
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }

            if (isInteraction1 && isInteraction2)
            {
                return(vectorDistanceMeasurementValues.InteractionAndInteraction);
            }

            if (!isInteraction1 && !isInteraction2)
            {
                return(vectorDistanceMeasurementValues.NonInteractionAndNonInteraction);
            }

            return(vectorDistanceMeasurementValues.InteractionAndNonInteraction);
        }
        public static double CustomDistanceOffset(bool[] array1, bool[] array2, VectorDistanceMeasurementValues vectorDistanceMeasurementValues, int offset)
        {
            if (array1 == null)
            {
                throw new ArgumentNullException(nameof(array1));
            }
            if (array2 == null)
            {
                throw new ArgumentNullException(nameof(array2));
            }
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }

            if (offset > array1.Length && offset > array2.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(offset));
            }

            if (array1 == null || array1.Length == 0)
            {
                throw new ArgumentOutOfRangeException(nameof(array1));
            }

            if (array2 == null || array2.Length == 0)
            {
                throw new ArgumentOutOfRangeException(nameof(array2));
            }

            bool[] shorterArray;
            bool[] longerArray;

            if (array1.Length > array2.Length)
            {
                if (offset + array2.Length > array1.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(offset));
                }

                longerArray  = array1;
                shorterArray = new bool[longerArray.Length];
                Array.Copy(array2, 0, shorterArray, offset, array2.Length);
            }
            else if (array2.Length > array1.Length)
            {
                if (offset + array1.Length > array2.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(offset));
                }

                longerArray  = array2;
                shorterArray = new bool[longerArray.Length];
                Array.Copy(array1, 0, shorterArray, offset, array1.Length);
            }
            else
            {
                shorterArray = array1;
                longerArray  = array2;
            }

            var distance = VectorDistanceValue(vectorDistanceMeasurementValues, shorterArray, longerArray);

            return(distance);
        }
        /*
         * public static void ClusterVectorDistanceMatrixUpgma(List<VectorProteinInterfaceWhole> vectorProteinInterfaceWholeList, decimal[,] vectorDistanceMatrix, int minimumOutputTreeLeafs, out List<string> vectorNames, out List<List<UpgmaNode>> nodeList, out List<List<string>> treeList, ProgressActionSet progressActionSet)
         * {
         *  if (vectorProteinInterfaceWholeList == null) throw new ArgumentNullException(nameof(vectorProteinInterfaceWholeList));
         *  if (vectorDistanceMatrix == null) throw new ArgumentNullException(nameof(vectorDistanceMatrix));
         *
         *  vectorNames = vectorProteinInterfaceWholeList.Select(VectorProteinInterfaceWholeTreeHeader).ToList();
         *
         *  List<string> finalTreeLeafOrderList;
         *  UpgmaClustering.Upgma(vectorDistanceMatrix, vectorNames, minimumOutputTreeLeafs, out nodeList, out treeList, out finalTreeLeafOrderList, false, progressActionSet);
         * }
         */

        public static void BestDistanceMatrixWithPartsAlignment(CancellationToken cancellationToken, List <VectorProteinInterfaceWhole> vectorProteinInterfaceWholeList, VectorDistanceMeasurementValues vectorDistanceMeasurementValues, out double[,] optimisticDistanceMatrix, /* out double[,] pessimisticDistanceMatrix,*/ ProgressActionSet progressActionSet)
        {
            if (vectorProteinInterfaceWholeList == null)
            {
                throw new ArgumentNullException(nameof(vectorProteinInterfaceWholeList));
            }
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }

            var totalVectors = vectorProteinInterfaceWholeList.Count;

            var optimisticDistanceMatrix2 = new double[totalVectors, totalVectors];
            //var pessimisticDistanceMatrix2 = new double[totalVectors, totalVectors];

            var workDivision = new WorkDivision(vectorProteinInterfaceWholeList.Count, -1);

            ProgressActionSet.StartAction(vectorProteinInterfaceWholeList.Count, progressActionSet);

            for (int threadIndex = 0; threadIndex < workDivision.ThreadCount; threadIndex++)
            {
                int localThreadIndex = threadIndex;

                var task = Task.Run(() =>
                {
                    for (int indexX = workDivision.ThreadFirstIndex[localThreadIndex]; indexX <= workDivision.ThreadLastIndex[localThreadIndex]; indexX++)
                    {
                        if (cancellationToken.IsCancellationRequested)
                        {
                            break;
                        }
                        var vectorProteinInterfaceWholeX = vectorProteinInterfaceWholeList[indexX];

                        for (int indexY = 0; indexY < vectorProteinInterfaceWholeList.Count; indexY++)
                        {
                            if (indexX >= indexY)
                            {
                                continue;
                            }

                            var vectorProteinInterfaceWholeY = vectorProteinInterfaceWholeList[indexY];

                            if (vectorProteinInterfaceWholeX.FullProteinInterfaceId == vectorProteinInterfaceWholeY.FullProteinInterfaceId)
                            {
                                continue;
                            }

                            double optimisticDistance;
                            //double pessimisticDistance;
                            BestDistanceWithPartsAlignment(vectorProteinInterfaceWholeX, vectorProteinInterfaceWholeY, vectorDistanceMeasurementValues, out optimisticDistance /*, out pessimisticDistance*/);

                            var lengthDifference = Math.Abs(vectorProteinInterfaceWholeX.ProteinInterfaceLength - vectorProteinInterfaceWholeY.ProteinInterfaceLength);

                            var lengthDistance = lengthDifference * vectorDistanceMeasurementValues.DifferentLengthProteinInterface;

                            optimisticDistance += lengthDistance;
                            //pessimisticDistance += lengthDistance;

                            optimisticDistanceMatrix2[indexX, indexY] = optimisticDistance;
                            //pessimisticDistanceMatrix2[indexX, indexY] = pessimisticDistance;

                            optimisticDistanceMatrix2[indexY, indexX] = optimisticDistance;
                            //pessimisticDistanceMatrix2[indexY, indexX] = pessimisticDistance;
                        }

                        workDivision.IncrementItemsCompleted(1);
                        ProgressActionSet.ProgressAction(1, progressActionSet);
                        ProgressActionSet.EstimatedTimeRemainingAction(workDivision.StartTicks, workDivision.ItemsCompleted, workDivision.ItemsToProcess, progressActionSet);
                    }
                }, cancellationToken);

                workDivision.TaskList.Add(task);
            }

            workDivision.WaitAllTasks();

            ProgressActionSet.FinishAction(true, progressActionSet);

            optimisticDistanceMatrix = optimisticDistanceMatrix2;
            //pessimisticDistanceMatrix = pessimisticDistanceMatrix2;
        }
        public static void BestDistanceWithPartsAlignment(VectorProteinInterfaceWhole vectorProteinInterfaceWhole1, VectorProteinInterfaceWhole vectorProteinInterfaceWhole2, VectorDistanceMeasurementValues vectorDistanceMeasurementValues, out double optimisticDistance /*, out double pessimisticDistance*/)
        {
            if (vectorProteinInterfaceWhole1 == null)
            {
                throw new ArgumentNullException(nameof(vectorProteinInterfaceWhole1));
            }
            if (vectorProteinInterfaceWhole2 == null)
            {
                throw new ArgumentNullException(nameof(vectorProteinInterfaceWhole2));
            }
            if (vectorDistanceMeasurementValues == null)
            {
                throw new ArgumentNullException(nameof(vectorDistanceMeasurementValues));
            }

            var proteinInterfaceLength1 = vectorProteinInterfaceWhole1.ProteinInterfaceLength;
            var proteinInterfaceLength2 = vectorProteinInterfaceWhole2.ProteinInterfaceLength;

            var proteinInterfaceLengthDifference = Math.Abs(proteinInterfaceLength1 - proteinInterfaceLength2);

            var longerProteinInterfaceLength  = proteinInterfaceLength1 > proteinInterfaceLength2 ? proteinInterfaceLength1 : proteinInterfaceLength2;
            var shorterProteinInterfaceLength = proteinInterfaceLength1 < proteinInterfaceLength2 ? proteinInterfaceLength1 : proteinInterfaceLength2;

            var longerProteinInterface  = proteinInterfaceLength1 > proteinInterfaceLength2 ? vectorProteinInterfaceWhole1 : vectorProteinInterfaceWhole2;
            var shorterProteinInterface = longerProteinInterface == vectorProteinInterfaceWhole1 ? vectorProteinInterfaceWhole2 : vectorProteinInterfaceWhole1;

            /*
             * A: Longer ProteinInterface
             * B: Shorter ProteinInterface
             *
             * A: Length 5
             *    0    1    2    3    4
             * A: 0000 0000 0000 0000 0000
             *
             * B: Length 3
             *       0    1    2    3    4 [proteinInterfacePartIndex]
             * B: 0: 0000 0000 0000 ____ ____ [shorterPartOffset = 0;] 3: proteinInterfacePartIndex>shorterProteinInterfaceLength+shorterPartOffset
             * B: 1: ____ 0000 0000 0000 ____ [shorterPartOffset = 1;] 1: proteinInterfacePartIndex<shorterPartOffset
             * B: 2: ____ ____ 0000 0000 0000 [shorterPartOffset = 2;]
             *    []
             *
             * LEN(A) - LEN(B) = 2
             *
             * 1. Loop through every proteinInterface part
             * 2. If (part offset > proteinInterface part index OR part offset)
             *
             */

            /* F 111000
             * F 101010
             *
             * F 111000
             * R 010101
             *
             * R 000111
             * R 010101
             *
             * R 000111
             * F 101010
             *
             */

            optimisticDistance = double.MaxValue;
            //pessimisticDistance = double.MinValue;
            const int directionCount = 1;

            for (var direction = 0; direction < directionCount; direction++)
            {
                for (var shorterPartOffset = 0; shorterPartOffset <= proteinInterfaceLengthDifference; shorterPartOffset++)
                {
                    double optimisticOffsetDistanceResult = 0;
                    //double pessimisticOffsetDistanceResult = 0;

                    for (var proteinInterfacePartIndex = 0; proteinInterfacePartIndex < longerProteinInterfaceLength; proteinInterfacePartIndex++)
                    {
                        double optimisticPartDistanceResult = 0;
                        //double pessimisticPartDistanceResult = 0;

                        if (proteinInterfacePartIndex < shorterPartOffset || proteinInterfacePartIndex >= shorterProteinInterfaceLength + shorterPartOffset)
                        {
                            var longerProteinInterfaceBools = longerProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex].InteractionFlagBools;

                            var distanceForProteinInterface = VectorDistanceValue(vectorDistanceMeasurementValues, longerProteinInterfaceBools, new bool[longerProteinInterfaceBools.Length]);

                            optimisticPartDistanceResult = distanceForProteinInterface;
                            //pessimisticPartDistanceResult = distanceForProteinInterface;

                            var distanceForNonProteinInterface = VectorDistanceValue(vectorDistanceMeasurementValues, longerProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex].InteractionToNonProteinInterface, false);

                            optimisticPartDistanceResult += distanceForNonProteinInterface;
                            //pessimisticPartDistanceResult += distanceForNonProteinInterface;
                        }
                        else
                        {
                            var longerProteinInterfaceBools  = longerProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex].InteractionFlagBools;
                            var shorterProteinInterfaceBools = shorterProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex - shorterPartOffset].InteractionFlagBools;

                            var longerProteinInterfaceBoolsCopy = new bool[longerProteinInterfaceBools.Length];
                            Array.Copy(longerProteinInterfaceBools, longerProteinInterfaceBoolsCopy, longerProteinInterfaceBools.Length);

                            var shorterProteinInterfaceBoolsCopy = new bool[shorterProteinInterfaceBools.Length];
                            Array.Copy(shorterProteinInterfaceBools, shorterProteinInterfaceBoolsCopy, shorterProteinInterfaceBools.Length);

                            if (direction == 1)
                            {
                                Array.Reverse(longerProteinInterfaceBoolsCopy);
                            }

                            double optimisticPartDistance;
                            //double pessimisticPartDistance;

                            CustomDistance(longerProteinInterfaceBoolsCopy, shorterProteinInterfaceBoolsCopy, vectorDistanceMeasurementValues, out optimisticPartDistance /*, out pessimisticPartDistance*/);

                            optimisticPartDistanceResult = optimisticPartDistance;
                            //pessimisticPartDistanceResult = pessimisticPartDistance;

                            var isInteractionToNonProteinInterface1 = longerProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex].InteractionToNonProteinInterface;
                            var isInteractionToNonProteinInterface2 = shorterProteinInterface.VectorProteinInterfacePartList[proteinInterfacePartIndex - shorterPartOffset].InteractionToNonProteinInterface;

                            var distanceForNonProteinInterface = VectorDistanceValue(vectorDistanceMeasurementValues, isInteractionToNonProteinInterface1, isInteractionToNonProteinInterface2);

                            optimisticPartDistanceResult += distanceForNonProteinInterface;
                            //pessimisticPartDistanceResult += distanceForNonProteinInterface;
                        }

                        optimisticOffsetDistanceResult += optimisticPartDistanceResult;
                        //pessimisticOffsetDistanceResult += pessimisticPartDistanceResult;
                    }

                    if (optimisticOffsetDistanceResult < optimisticDistance)
                    {
                        optimisticDistance = optimisticOffsetDistanceResult;
                    }

                    //if (pessimisticOffsetDistanceResult > pessimisticDistance)
                    //{
                    //    pessimisticDistance = pessimisticOffsetDistanceResult;
                    //}
                }
            }
        }