private void WriteMetricValue(MetricValueV2 value, bool useDouble, bool storeDoubleAsLong, BinaryWriter writer)
 {
     if (useDouble)
     {
         if (storeDoubleAsLong)
         {
             SerializationUtils.WriteInt64AsBase128(writer, (long)value.ValueAsDouble);
         }
         else
         {
             writer.Write(value.ValueAsDouble);
         }
     }
     else
     {
         SerializationUtils.WriteUInt64AsBase128(writer, value.ValueAsULong);
     }
 }
        private void WriteMetricsData(BinaryWriter writer, IEnumerable <IReadOnlyMetric> metricData)
        {
            Stream writerStream = writer.BaseStream;

            long currentTimeInMinutes = 0;

            if (this.version >= 5)
            {
                var currentTimeInTicks = DateTime.UtcNow.Ticks;
                currentTimeInMinutes = (currentTimeInTicks - (currentTimeInTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval;
                SerializationUtils.WriteUInt64AsBase128(writer, (ulong)currentTimeInMinutes);
            }

            var metricsCountPosition = writer.BaseStream.Position;

            writer.Write((uint)0);
            uint metricsCount = 0;

            foreach (var data in metricData)
            {
                ++metricsCount;
                var metadata = data.MetricMetadata;

                this.WriteMetricMetadataIndex(writer, metadata);

                // In versions 0-2 Monitoring Account and Metric Namespace was part of the Metric data
                // From version 3 Monitoring Account is removed and Metric Namespace became a part of Metric Metadata
                if (this.version < 3)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MonitoringAccount));
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MetricNamespace));
                }

                // In version 0 we had EventId, which was always passed as empty string
                if (this.version == 0)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(string.Empty));
                }

                if (this.version >= 5)
                {
                    var  timeTicks     = data.TimeUtc.Ticks;
                    long timeInMinutes = (timeTicks - (timeTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval;
                    SerializationUtils.WriteInt64AsBase128(writer, currentTimeInMinutes - timeInMinutes);
                }
                else
                {
                    SerializationUtils.WriteUInt64AsBase128(writer, (ulong)data.TimeUtc.Ticks);
                }

                for (byte j = 0; j < data.MetricMetadata.DimensionsCount; ++j)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.GetDimensionValue(j)));
                }

                var  samplingTypes     = data.SamplingTypes;
                bool useDouble         = false;
                bool storeDoubleAsLong = false;
                if ((samplingTypes & SamplingTypes.DoubleValueType) != 0)
                {
                    useDouble = true;

                    if (data.SumUnion.CanRepresentDoubleAsLong() &&
                        ((samplingTypes & SamplingTypes.Min) == 0 ||
                         (data.MinUnion.CanRepresentDoubleAsLong() && data.MaxUnion.CanRepresentDoubleAsLong())))
                    {
                        samplingTypes     = samplingTypes | SamplingTypes.DoubleValueStoredAsLongType;
                        storeDoubleAsLong = true;
                    }
                }

                SerializationUtils.WriteUInt32AsBase128(writer, (uint)samplingTypes);

                if ((data.SamplingTypes & SamplingTypes.Min) != 0)
                {
                    this.WriteMetricValue(data.MinUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Max) != 0)
                {
                    this.WriteMetricValue(data.MaxUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Sum) != 0)
                {
                    this.WriteMetricValue(data.SumUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Count) != 0)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, data.Count);
                }

                if ((data.SamplingTypes & SamplingTypes.SumOfSquareDiffFromMean) != 0)
                {
                    writer.Write(data.SumOfSquareDiffFromMean);
                }

                if ((data.SamplingTypes & SamplingTypes.Histogram) != 0)
                {
                    if (data.Histogram == null)
                    {
                        var message = string.Format(
                            CultureInfo.InvariantCulture,
                            "Invalid input data. Declared sampling type contains Histogram, but Histogram data is null. Metric:({0},{1},{2}).",
                            data.MonitoringAccount,
                            data.MetricNamespace,
                            data.MetricMetadata.MetricName);

                        throw new MetricSerializationException(message, null);
                    }

                    SerializationUtils.WriteHistogramDataHistogram(writer, data.Histogram.SamplesCount, data.Histogram.Samples, this.version > 3);
                }

                if ((data.SamplingTypes & SamplingTypes.HyperLogLogSketch) != 0)
                {
                    if (data.HyperLogLogSketchesStream == null && data.HyperLogLogSketches == null)
                    {
                        var message = string.Format(
                            CultureInfo.InvariantCulture,
                            "Invalid input data. Declared sampling type contains sketches, but sketches data is null. Metric:({0},{1},{2}).",
                            data.MonitoringAccount,
                            data.MetricNamespace,
                            data.MetricMetadata.MetricName);
                        throw new MetricSerializationException(message, null);
                    }

                    if (data.HyperLogLogSketchesStream != null)
                    {
                        if (this.tempBuffer == null)
                        {
                            this.tempBuffer = new byte[TempBufferSize];
                        }

                        writer.Write((int)data.HyperLogLogSketchesStream.Length);
                        var sketchStreamStartPosition = data.HyperLogLogSketchesStream.Position;
                        SerializationUtils.ReadFromStream(data.HyperLogLogSketchesStream, writer.BaseStream, (int)data.HyperLogLogSketchesStream.Length, this.tempBuffer);
                        data.HyperLogLogSketchesStream.Position = sketchStreamStartPosition;
                    }
                    else
                    {
                        if (this.hllSerializationVersion == 0)
                        {
                            SerializationUtils.WriteHyperLogLogSketches(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches);
                        }
                        else
                        {
                            SerializationUtils.WriteHyperLogLogSketchesV2(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches);
                        }
                    }
                }

                if (this.version >= 6)
                {
                    if ((data.SamplingTypes & samplingTypes & SamplingTypes.TDigest) != 0)
                    {
                        SerializationUtils.WriteUInt32AsBase128(writer, FrontEndMetricDeserializer <IMetricMetadata> .TDigestPrefixValue);

                        long pos = writerStream.Position;

                        // placeholder for length encoded as 4 bytes
                        writer.Write((ushort)0);
                        writer.Write((ushort)0);

                        data.TDigest.Serialize(writer);
                        long tdigestSerializedLength = writerStream.Position - pos - 4;
                        if (tdigestSerializedLength > ushort.MaxValue)
                        {
                            throw new ArgumentException("TDigest too big");
                        }

                        writerStream.Position = pos;
                        SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, (ushort)tdigestSerializedLength);

                        writerStream.Position += tdigestSerializedLength;
                    }

                    SerializationUtils.WriteUInt32AsBase128(writer, 0);
                }

                this.currentMetricDataBlockSize = writer.BaseStream.Position;
            }

            var currentPosition = writer.BaseStream.Position;

            writer.BaseStream.Position = metricsCountPosition;

            // Versions before 2 used variable number of bytes to write number of serialized metrics data.
            // From version 2 passing IEnumerable<IReadOnlyMetric> is supported, thus number of metrics data
            // is unknown beforehand and we cannot use variable number anymore. Thus we use fixed 4 bytes
            // uint. To keep compatibility with previous versions, while still have ability to serialize
            // variable amount of data, we write uint number in variable manner but with fixed number of bytes.
            if (this.version >= 2)
            {
                writer.Write(metricsCount);
            }
            else
            {
                SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, metricsCount);
            }

            writer.BaseStream.Position = currentPosition;
        }