/// <summary>
        /// Gets densities for segments with size equal to <paramref name="segmentSize"/>
        /// </summary>
        /// <param name="buckets">Array of buckets</param>
        /// <param name="start">Start timestamp of the event range we want to find. If null, then it is a first event time in the first bucket of the buckets array.</param>
        /// <param name="segmentSize">Length/duration of one segment</param>
        /// <param name="finalize">Flag indicating that density should be calculated for the last incomplete segment if there is one</param>
        /// <param name="nextBatchStartTime">Next start time</param>
        /// <returns></returns>
        public static double[] GetDensities(ArraySegment <Bucket> bucketsArray, long?start, long segmentSize, bool finalize, out long nextBatchStartTime)
        {
            var buckets = bucketsArray.AsSpan();

            if (buckets.Length < 1)
            {
                throw new ArgumentException("Empty array of buckets is not allowed");
            }

            if (!start.HasValue)
            {
                start = buckets[0].GetAbsoluteTimeForEvent(buckets[0].GetFirstEvent());
            }

            long end = buckets[buckets.Length - 1].GetAbsoluteTimeForEvent(buckets[buckets.Length - 1].GetLastEvent()) + 1;

            ushort totalSegments = 0;

            try
            {
                if (finalize)
                {
                    totalSegments = checked ((ushort)Math.Ceiling((end - start.Value) / (double)segmentSize));
                }
                else
                {
                    totalSegments = checked ((ushort)Math.Floor((end - start.Value) / (double)segmentSize));
                }
            }
            catch (OverflowException)
            {
                throw new ArgumentException("Too small segment size for such a big range", nameof(segmentSize));
            }

            var densitiesBuf = new double[totalSegments];

            DensityCalculator.CalculateDensities(
                bucketsArray.AsMemory(),
                new DensityCalculationRequest(start.Value, end, segmentSize),
                densitiesBuf,
                finalize,
                out long processedRange
                );

            nextBatchStartTime = start.Value + processedRange;

            return(densitiesBuf);
        }
        /// <summary>
        /// Gets densities for segments with size equal to <paramref name="segmentSize"/>
        /// </summary>
        /// <param name="container">Bucket container</param>
        /// <param name="start">Start time</param>
        /// <param name="end">End time</param>
        /// <param name="segmentSize">Length/duration of one segment</param>
        /// <param name="targetBuffer">Targer buffer into  which calculated densities are saved</param>
        /// <returns></returns>
        public static Span <double> GetDensities(BucketContainer container, long start, long end, long segmentSize, ref double[] targetBuffer)
        {
            if (end <= start)
            {
                throw new ArgumentException("Wrong interval. End timestamp must be greater than start timestamp.");
            }

            if (end - start < segmentSize)
            {
                throw new ArgumentException("Segment size is too big for this time interval", nameof(segmentSize));
            }


            var lastBucket = container.GetLastBucket();
            var maxTime    = lastBucket.GetAbsoluteTimeForEvent(lastBucket.GetLastEvent());

            if (end > maxTime + 1)
            {
                end = maxTime + 1;
            }

            // Start time is out of range
            if (end < start)
            {
                return(Span <double> .Empty);
            }

            ushort totalSegments = GetTotalSegments(start, end, segmentSize);

            if (targetBuffer == null)
            {
                targetBuffer = new double[totalSegments];
            }
            else if (targetBuffer.Length < totalSegments)
            {
                throw new ArgumentException("Target buffer is too short", nameof(targetBuffer));
            }

            int processedSegmentsUsingHints = -1;

            try
            {
                checked
                {
                    container.DensityHintContainer?.TrySetDensitiesUsingHints(
                        start - container.FirstTimestamp,
                        end - container.FirstTimestamp,
                        segmentSize,
                        targetBuffer,
                        out processedSegmentsUsingHints
                        );
                }
            }
            catch (OverflowException)
            {
                throw new InvalidOperationException("Too big range of events");
            }

#if DEBUG
            if (processedSegmentsUsingHints == 0 && segmentSize >= 10_000_000)
            {
                Debug.Fail("Density calculation is perfoming in unoptimized way. It can be eliminated by adjusting range and segment size to appropriate values.");
            }
#endif

            if (processedSegmentsUsingHints < 0)
            {
                processedSegmentsUsingHints = 0;
            }

            if (processedSegmentsUsingHints == totalSegments)
            {
                return(targetBuffer.AsSpan(0, totalSegments));
            }

            start += processedSegmentsUsingHints * segmentSize;
            int uncalculatedDensitiesCount = totalSegments - processedSegmentsUsingHints;

            var targetBufferCopy = targetBuffer;
            Parallel.ForEach(GetPartitions(start, segmentSize, targetBuffer.AsSpan(processedSegmentsUsingHints, uncalculatedDensitiesCount)), (partition) =>
            {
                Span <double> batch = targetBufferCopy.AsSpan(processedSegmentsUsingHints, uncalculatedDensitiesCount)
                                      .Slice(partition.leftBoundary, partition.batchLength);

                DensityCalculator.CalculateDensities(
                    container.Buckets,
                    new DensityCalculationRequest(partition.start, partition.end, segmentSize),
                    batch,
                    true,
                    out long processedRange
                    );
            });


            return(targetBuffer.AsSpan(0, totalSegments));
        }