private MetricsGossipEnvelope MetricsGossipEnvelopeFromProto(MetricsGossipEnvelope envelope)
        {
            var gossip            = envelope.Gossip;
            var addressMapping    = gossip.AllAddresses.Select(AddressFromProto).ToImmutableArray();
            var metricNameMapping = gossip.AllMetricNames.ToImmutableArray();

            Option <NodeMetrics.Types.EWMA> EwmaFromProto(NodeMetrics.Types.EWMA ewma)
            => new NodeMetrics.Types.EWMA(ewma.Value, ewma.Alpha);

            AnyNumber NumberFromProto(NodeMetrics.Types.Number number)
            {
                switch (number.Type)
                {
                case NodeMetrics.Types.NumberType.Double:
                    return(BitConverter.Int64BitsToDouble((long)number.Value64));

                case NodeMetrics.Types.NumberType.Float:
                    return(BitConverter.ToSingle(BitConverter.GetBytes((int)number.Value32), 0));

                case NodeMetrics.Types.NumberType.Integer:
                    return(Convert.ToInt32(number.Value32));

                case NodeMetrics.Types.NumberType.Long:
                    return(Convert.ToInt64(number.Value64));

                case NodeMetrics.Types.NumberType.Serialized:
                    // TODO: Should we somehow port this?

                    /*val in = new ClassLoaderObjectInputStream(
                     *  system.dynamicAccess.classLoader,
                     *  new ByteArrayInputStream(number.getSerialized.toByteArray))
                     * val obj = in.readObject
                     *  in.close()
                     * obj.asInstanceOf[jl.Number]*/
                    throw new NotImplementedException($"{NodeMetrics.Types.NumberType.Serialized} number type is not supported");

                default:
                    throw new ArgumentOutOfRangeException(nameof(number));
                }
            }

            NodeMetrics.Types.Metric MetricFromProto(NodeMetrics.Types.Metric metric)
            {
                return(new NodeMetrics.Types.Metric(
                           metricNameMapping[metric.NameIndex],
                           NumberFromProto(metric.Number),
                           metric.Ewma != null ? EwmaFromProto(metric.Ewma) : Option <NodeMetrics.Types.EWMA> .None));
            }

            NodeMetrics NodeMetricsFromProto(NodeMetrics metrics)
            {
                return(new NodeMetrics(
                           addressMapping[metrics.AddressIndex],
                           metrics.Timestamp,
                           metrics.Metrics.Select(MetricFromProto).ToImmutableArray()));
            }

            var nodeMetrics = gossip.NodeMetrics.Select(NodeMetricsFromProto).ToImmutableHashSet();

            return(new MetricsGossipEnvelope(AddressFromProto(envelope.From), new MetricsGossip(nodeMetrics), envelope.Reply));
        }
        private MetricsGossipEnvelope MetricsGossipEnvelopeToProto(MetricsGossipEnvelope envelope)
        {
            var allNodeMetrics = envelope.Gossip.Nodes;
            var allAddresses   = allNodeMetrics.Select(m => m.Address).ToImmutableArray();
            var addressMapping = allAddresses.Select((a, i) => (Index: i, Value: a)).ToImmutableDictionary(p => p.Value, p => p.Index);
            var allMetricNames = allNodeMetrics.Aggregate(
                ImmutableHashSet <string> .Empty,
                (set, metrics) => set.Union(metrics.Metrics.Select(m => m.Name))).ToImmutableArray();
            var metricNamesMapping = allMetricNames.Select((a, i) => (Index: i, Value: a)).ToImmutableDictionary(p => p.Value, p => p.Index);

            int MapAddress(Actor.Address address) => MapWithErrorMessage(addressMapping, address, "address");
            int MapName(string name) => MapWithErrorMessage(metricNamesMapping, name, "metric name");

            Option <NodeMetrics.Types.EWMA> EwmaToProto(Option <NodeMetrics.Types.EWMA> ewma)
            => ewma.Select(e => new NodeMetrics.Types.EWMA(e.Value, e.Alpha));

            NodeMetrics.Types.Number NumberToProto(AnyNumber number)
            {
                var proto = new NodeMetrics.Types.Number();

                switch (number.Type)
                {
                case AnyNumber.NumberType.Int:
                    proto.Type    = NodeMetrics.Types.NumberType.Integer;
                    proto.Value32 = Convert.ToUInt32(number.LongValue);
                    break;

                case AnyNumber.NumberType.Long:
                    proto.Type    = NodeMetrics.Types.NumberType.Long;
                    proto.Value64 = Convert.ToUInt64(number.LongValue);
                    break;

                case AnyNumber.NumberType.Float:
                    proto.Type    = NodeMetrics.Types.NumberType.Float;
                    proto.Value32 = (uint)BitConverter.ToInt32(BitConverter.GetBytes((float)number.DoubleValue), 0);
                    break;

                case AnyNumber.NumberType.Double:
                    proto.Type    = NodeMetrics.Types.NumberType.Double;
                    proto.Value64 = (ulong)BitConverter.DoubleToInt64Bits(number.DoubleValue);
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                return(proto);
            }

            NodeMetrics.Types.Metric MetricToProto(NodeMetrics.Types.Metric m)
            {
                var metric = new NodeMetrics.Types.Metric()
                {
                    NameIndex = MapName(m.Name),
                    Number    = NumberToProto(m.Value),
                };

                var ewma = EwmaToProto(m.Average);

                if (ewma.HasValue)
                {
                    metric.Ewma = ewma.Value;
                }

                return(metric);
            }

            NodeMetrics NodeMetricsToProto(NodeMetrics nodeMetrics)
            {
                return(new NodeMetrics()
                {
                    AddressIndex = MapAddress(nodeMetrics.Address),
                    Timestamp = nodeMetrics.Timestamp,
                    Metrics = { nodeMetrics.Metrics.Select(MetricToProto) }
                });
            }

            var nodeMetricsProto = allNodeMetrics.Select(NodeMetricsToProto);

            return(new MetricsGossipEnvelope()
            {
                From = AddressToProto(envelope.FromAddress),
                Reply = envelope.Reply,
                Gossip = new MetricsGossip()
                {
                    AllAddresses = { allAddresses.Select(AddressToProto) },
                    AllMetricNames = { allMetricNames },
                    NodeMetrics = { nodeMetricsProto }
                }
            });
        }