/// <summary>
        /// CalculateDistance is a function that claculates the distance between two elements of same size.
        /// </summary>
        /// <param name="FirstElement">first element</param>
        /// <param name="SecondElement">second element</param>
        /// <returns>
        /// - Item 1: distance between the two elements
        /// - Item 2: AnomalyDetectionResponse: a code and a message that state whether the function succeeded or encountered an error
        ///     when the function succeeds, it will return:
        ///     - Code: 0, "OK"</returns>
        internal static Tuple <double, AnomalyDetectionResponse> CalculateDistance(double[] FirstElement, double[] SecondElement)
        {
            AnomalyDetectionResponse ADResponse;

            try
            {
                if (FirstElement == null || SecondElement == null)
                {
                    ADResponse = new AnomalyDetectionResponse(101, "Function <CalculateDistance>: At least one input is null");
                    return(Tuple.Create(-1.0, ADResponse));
                }

                if (FirstElement.Length != SecondElement.Length)
                {
                    ADResponse = new AnomalyDetectionResponse(115, "Function <CalculateDistance>: Inputs have different dimensions");
                    return(Tuple.Create(-1.0, ADResponse));
                }
                double SquaredDistance = 0;
                for (int i = 0; i < FirstElement.Length; i++)
                {
                    SquaredDistance += Math.Pow(FirstElement[i] - SecondElement[i], 2);
                }
                ADResponse = new AnomalyDetectionResponse(0, "OK");
                return(Tuple.Create(Math.Sqrt(SquaredDistance), ADResponse));
            }
            catch (Exception Ex)
            {
                ADResponse = new AnomalyDetectionResponse(400, "Function <CalculateDistance>: Unhandled exception:\t" + Ex.ToString());
                return(Tuple.Create(-1.0, ADResponse));
            }
        }
        /// <summary>
        /// CalculateNearestCluster is a function that determines the nearest cluster and calculates the distance between those two clusters.
        /// </summary>
        /// <param name="Centroids">the centroids of the clusters</param>
        /// <param name="SamplesInClusters">number of samples in each cluster</param>
        /// <returns>Tuple of three Items:
        /// - Item 1: contains the number of nearest cluster
        /// - Item 2: contains the distance to the nearest cluster
        /// - Item 3: AnomalyDetectionResponse: a code and a message that state whether the function succeeded or encountered an error
        ///     when the function succeeds, it will return:
        ///     - Code: 0, "OK"</returns>
        private static Tuple <int[], double[], AnomalyDetectionResponse> CalculateNearestCluster(double[][] Centroids, int[] SamplesInClusters)
        {
            AnomalyDetectionResponse ADResponse;

            int[]    NearestClustersArray          = new int[Centroids.Length];
            double[] DistanceToNearestClusterArray = new double[Centroids.Length];
            try
            {
                Tuple <double, AnomalyDetectionResponse> CDResponse;
                for (int i = 0; i < Centroids.Length; i++)
                {
                    //in case of empty cluster
                    if (SamplesInClusters[i] == 0)
                    {
                        NearestClustersArray[i]          = -1;
                        DistanceToNearestClusterArray[i] = -1;
                        continue;
                    }
                    DistanceToNearestClusterArray[i] = double.MaxValue;
                    for (int j = 0; j < Centroids.Length; j++)
                    {
                        if (i == j || SamplesInClusters[j] == 0)
                        {
                            continue;
                        }
                        CDResponse = CalculateDistance(Centroids[i], Centroids[j]);
                        if (CDResponse.Item2.Code != 0)
                        {
                            NearestClustersArray          = null;
                            DistanceToNearestClusterArray = null;
                            return(Tuple.Create(NearestClustersArray, DistanceToNearestClusterArray, CDResponse.Item2));
                        }
                        if (CDResponse.Item1 < DistanceToNearestClusterArray[i])
                        {
                            DistanceToNearestClusterArray[i] = CDResponse.Item1;
                            NearestClustersArray[i]          = j;
                        }
                    }
                }
                ADResponse = new AnomalyDetectionResponse(0, "OK");
                return(Tuple.Create(NearestClustersArray, DistanceToNearestClusterArray, ADResponse));
            }
            catch (Exception Ex)
            {
                NearestClustersArray          = null;
                DistanceToNearestClusterArray = null;
                ADResponse = new AnomalyDetectionResponse(400, "Function <CalculateNearestCluster>: Unhandled exception:\t" + Ex.ToString());
                return(Tuple.Create(NearestClustersArray, DistanceToNearestClusterArray, ADResponse));
            }
        }
        /// <summary>
        /// SamplesInCLusterNumber is a function that counts the number of samples of each cluster.
        /// </summary>
        /// <param name="RawData">data to be clustered</param>
        /// <param name="DataToClusterMapping">contains the assigned cluster number for each sample of the RawData</param>
        /// <param name="NumberOfClusters">the number of clusters</param>
        /// <returns>Tuple of two Items:
        /// - Item 1: contains the number of samples for each cluster
        /// - Item 2: AnomalyDetectionResponse: a code and a message that state whether the function succeeded or encountered an error
        ///     when the function succeeds, it will return:
        ///     - Code: 0, "OK"</returns>
        private static Tuple <int[], AnomalyDetectionResponse> SamplesInClusterNumber(double[][] RawData, int[] DataToClusterMapping, int NumberOfClusters)
        {
            AnomalyDetectionResponse ADResponse;

            int[] ClusterSamplesCounter;
            try
            {
                ClusterSamplesCounter = new int[NumberOfClusters];
                for (int i = 0; i < DataToClusterMapping.Length; i++)
                {
                    ClusterSamplesCounter[DataToClusterMapping[i]]++;
                }
                ADResponse = new AnomalyDetectionResponse(0, "OK");
                return(Tuple.Create(ClusterSamplesCounter, ADResponse));
            }
            catch (Exception Ex)
            {
                ClusterSamplesCounter = null;
                ADResponse            = new AnomalyDetectionResponse(400, "Function <SamplesInClusterNumber>: Unhandled exception:\t" + Ex.ToString());
                return(Tuple.Create(ClusterSamplesCounter, ADResponse));
            }
        }
        /// <summary>
        /// CreateClusteringResult is a function that generates the clustering results using several subfunctions.
        /// </summary>
        /// <param name="RawData">data to be clustered</param>
        /// <param name="DataToClusterMapping">contains the assigned cluster number for each sample of the RawData</param>
        /// <param name="Centroids">the centroids of the clusters</param>
        /// <param name="NumberOfClusters">the number of clusters</param>
        /// <returns>Tuple of two Items:
        /// - Item 1: contains the results for each cluster
        /// - Item 2: AnomalyDetectionResponse: a code and a message that state whether the function succeeded or encountered an error
        ///     when the function succeeds, it will return:
        ///     - Code: 0, "OK"</returns>
        public static Tuple <ClusteringResults[], AnomalyDetectionResponse> CreateClusteringResult(double[][] RawData, int[] DataToClusterMapping, double[][] Centroids, int NumberOfClusters)
        {
            ClusteringResults[]      Results;
            AnomalyDetectionResponse ADResponse;

            try
            {
                //get how many samples are there in each cluster
                Tuple <int[], AnomalyDetectionResponse> SICNResponse = SamplesInClusterNumber(RawData, DataToClusterMapping, NumberOfClusters);
                if (SICNResponse.Item2.Code != 0)
                {
                    Results = null;
                    return(Tuple.Create(Results, SICNResponse.Item2));
                }
                int[] ClusterSamplesCounter = SICNResponse.Item1;
                Results = new ClusteringResults[NumberOfClusters];
                for (int i = 0; i < NumberOfClusters; i++)
                {
                    Results[i]             = new ClusteringResults();
                    Results[i].Centroid    = Centroids[i];
                    Results[i].ClusterData = new double[ClusterSamplesCounter[i]][];
                    Results[i].ClusterDataOriginalIndex = new int[ClusterSamplesCounter[i]];
                    //group the samples of the cluster
                    ADResponse = Results[i].AssignSamplesToClusters(RawData, DataToClusterMapping, i);
                    if (ADResponse.Code != 0)
                    {
                        Results = null;
                        return(Tuple.Create(Results, ADResponse));
                    }
                    ADResponse = Results[i].CalculateStatistics();
                    if (ADResponse.Code != 0)
                    {
                        Results = null;
                        return(Tuple.Create(Results, ADResponse));
                    }
                }

                //use functions to calculate the properties and statistics of each clusters.
                Tuple <int[], double[], AnomalyDetectionResponse> CNCResponse = CalculateNearestCluster(Centroids, ClusterSamplesCounter);
                if (CNCResponse.Item3.Code != 0)
                {
                    Results = null;
                    return(Tuple.Create(Results, CNCResponse.Item3));
                }
                for (int i = 0; i < NumberOfClusters; i++)
                {
                    Results[i].NearestCluster = CNCResponse.Item1[i];
                    Results[i].DistanceToNearestClusterCentroid = CNCResponse.Item2[i];
                }

                double[][] NearestForeignSampleInNearestClusterArray;
                double[]   DistanceToNearestForeignSampleInNearestClusterArray;
                double[][] NearestForeignSampleArray;
                double[]   DistanceToNearestForeignSampleArray;
                int[]      ClusterOfNearestForeignSampleArray;
                ADResponse = CalculateMoreStatistics(RawData, DataToClusterMapping, Centroids, CNCResponse.Item1, out NearestForeignSampleInNearestClusterArray, out DistanceToNearestForeignSampleInNearestClusterArray, out NearestForeignSampleArray, out DistanceToNearestForeignSampleArray, out ClusterOfNearestForeignSampleArray);
                if (ADResponse.Code != 0)
                {
                    Results = null;
                    return(Tuple.Create(Results, ADResponse));
                }
                for (int i = 0; i < NumberOfClusters; i++)
                {
                    Results[i].NearestForeignSampleInNearestCluster           = NearestForeignSampleInNearestClusterArray[i];
                    Results[i].DistanceToNearestForeignSampleInNearestCluster = DistanceToNearestForeignSampleInNearestClusterArray[i];
                    Results[i].NearestForeignSample           = NearestForeignSampleArray[i];
                    Results[i].DistanceToNearestForeignSample = DistanceToNearestForeignSampleArray[i];
                    Results[i].ClusterOfNearestForeignSample  = ClusterOfNearestForeignSampleArray[i];
                }

                ADResponse = new AnomalyDetectionResponse(0, "OK");
                return(Tuple.Create(Results, ADResponse));
            }
            catch (Exception Ex)
            {
                Results    = null;
                ADResponse = new AnomalyDetectionResponse(400, "Function <CreateClusteringResult>: Unhandled exception:\t" + Ex.ToString());
                return(Tuple.Create(Results, ADResponse));
            }
        }