// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.io;

import java.util.Iterator;
import javax.annotation.Nullable;
import com.hypixel.hytale.protocol.PacketRegistry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.Queue;
import com.hypixel.hytale.metrics.metric.AverageCollector;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicInteger;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
import java.util.ArrayList;
import javax.annotation.Nonnull;
import com.hypixel.hytale.metrics.MetricsRegistry;
import com.hypixel.hytale.protocol.io.PacketStatsRecorder;

public class PacketStatsRecorderImpl implements PacketStatsRecorder
{
    public static final MetricsRegistry<PacketStatsRecorderImpl> METRICS_REGISTRY;
    private final PacketStatsEntry[] entries;
    
    public PacketStatsRecorderImpl() {
        this.entries = new PacketStatsEntry[512];
        for (int i = 0; i < this.entries.length; ++i) {
            this.entries[i] = new PacketStatsEntry(i);
        }
    }
    
    @Override
    public void recordSend(final int packetId, final int uncompressedSize, final int compressedSize) {
        if (packetId < 0 || packetId >= this.entries.length) {
            return;
        }
        this.entries[packetId].recordSend(uncompressedSize, compressedSize);
    }
    
    @Override
    public void recordReceive(final int packetId, final int uncompressedSize, final int compressedSize) {
        if (packetId < 0 || packetId >= this.entries.length) {
            return;
        }
        this.entries[packetId].recordReceive(uncompressedSize, compressedSize);
    }
    
    @Nonnull
    @Override
    public PacketStatsEntry getEntry(final int packetId) {
        return this.entries[packetId];
    }
    
    static {
        METRICS_REGISTRY = new MetricsRegistry<PacketStatsRecorderImpl>().register("Packets", recorder -> {
            final ArrayList entries = new ArrayList();
            for (int i = 0; i < 512; ++i) {
                final PacketStatsEntry entry = recorder.entries[i];
                if (entry.hasData()) {
                    entries.add(entry);
                }
            }
            return (Object[])entries.toArray(PacketStatsEntry[]::new);
        }, (Codec<PacketStatsEntry[]>)new ArrayCodec<PacketStatsEntry>(PacketStatsEntry.METRICS_REGISTRY, PacketStatsEntry[]::new));
    }
    
    public static class PacketStatsEntry implements PacketStatsRecorder.PacketStatsEntry
    {
        public static final MetricsRegistry<PacketStatsEntry> METRICS_REGISTRY;
        private final int packetId;
        private final AtomicInteger sentCount;
        private final AtomicLong sentUncompressedTotal;
        private final AtomicLong sentCompressedTotal;
        private final AtomicLong sentUncompressedMin;
        private final AtomicLong sentUncompressedMax;
        private final AtomicLong sentCompressedMin;
        private final AtomicLong sentCompressedMax;
        private final AverageCollector sentUncompressedAvg;
        private final AverageCollector sentCompressedAvg;
        private final Queue<SizeRecord> sentRecently;
        private final AtomicInteger receivedCount;
        private final AtomicLong receivedUncompressedTotal;
        private final AtomicLong receivedCompressedTotal;
        private final AtomicLong receivedUncompressedMin;
        private final AtomicLong receivedUncompressedMax;
        private final AtomicLong receivedCompressedMin;
        private final AtomicLong receivedCompressedMax;
        private final AverageCollector receivedUncompressedAvg;
        private final AverageCollector receivedCompressedAvg;
        private final Queue<SizeRecord> receivedRecently;
        
        public PacketStatsEntry(final int packetId) {
            this.sentCount = new AtomicInteger();
            this.sentUncompressedTotal = new AtomicLong();
            this.sentCompressedTotal = new AtomicLong();
            this.sentUncompressedMin = new AtomicLong(Long.MAX_VALUE);
            this.sentUncompressedMax = new AtomicLong();
            this.sentCompressedMin = new AtomicLong(Long.MAX_VALUE);
            this.sentCompressedMax = new AtomicLong();
            this.sentUncompressedAvg = new AverageCollector();
            this.sentCompressedAvg = new AverageCollector();
            this.sentRecently = new ConcurrentLinkedQueue<SizeRecord>();
            this.receivedCount = new AtomicInteger();
            this.receivedUncompressedTotal = new AtomicLong();
            this.receivedCompressedTotal = new AtomicLong();
            this.receivedUncompressedMin = new AtomicLong(Long.MAX_VALUE);
            this.receivedUncompressedMax = new AtomicLong();
            this.receivedCompressedMin = new AtomicLong(Long.MAX_VALUE);
            this.receivedCompressedMax = new AtomicLong();
            this.receivedUncompressedAvg = new AverageCollector();
            this.receivedCompressedAvg = new AverageCollector();
            this.receivedRecently = new ConcurrentLinkedQueue<SizeRecord>();
            this.packetId = packetId;
        }
        
        void recordSend(final int uncompressedSize, final int compressedSize) {
            this.sentCount.incrementAndGet();
            this.sentUncompressedTotal.addAndGet(uncompressedSize);
            this.sentCompressedTotal.addAndGet(compressedSize);
            this.sentUncompressedMin.accumulateAndGet(uncompressedSize, Math::min);
            this.sentUncompressedMax.accumulateAndGet(uncompressedSize, Math::max);
            this.sentCompressedMin.accumulateAndGet(compressedSize, Math::min);
            this.sentCompressedMax.accumulateAndGet(compressedSize, Math::max);
            this.sentUncompressedAvg.add(uncompressedSize);
            this.sentCompressedAvg.add(compressedSize);
            final long now = System.nanoTime();
            this.sentRecently.add(new SizeRecord(now, uncompressedSize, compressedSize));
            this.pruneOld(this.sentRecently, now);
        }
        
        void recordReceive(final int uncompressedSize, final int compressedSize) {
            this.receivedCount.incrementAndGet();
            this.receivedUncompressedTotal.addAndGet(uncompressedSize);
            this.receivedCompressedTotal.addAndGet(compressedSize);
            this.receivedUncompressedMin.accumulateAndGet(uncompressedSize, Math::min);
            this.receivedUncompressedMax.accumulateAndGet(uncompressedSize, Math::max);
            this.receivedCompressedMin.accumulateAndGet(compressedSize, Math::min);
            this.receivedCompressedMax.accumulateAndGet(compressedSize, Math::max);
            this.receivedUncompressedAvg.add(uncompressedSize);
            this.receivedCompressedAvg.add(compressedSize);
            final long now = System.nanoTime();
            this.receivedRecently.add(new SizeRecord(now, uncompressedSize, compressedSize));
            this.pruneOld(this.receivedRecently, now);
        }
        
        private void pruneOld(final Queue<SizeRecord> queue, final long now) {
            final long cutoff = now - TimeUnit.SECONDS.toNanos(30L);
            for (SizeRecord head = queue.peek(); head != null && head.nanos < cutoff; head = queue.peek()) {
                queue.poll();
            }
        }
        
        @Override
        public boolean hasData() {
            return this.sentCount.get() > 0 || this.receivedCount.get() > 0;
        }
        
        @Override
        public int getPacketId() {
            return this.packetId;
        }
        
        @Nullable
        @Override
        public String getName() {
            final PacketRegistry.PacketInfo info = PacketRegistry.getById(this.packetId);
            return (info != null) ? info.name() : null;
        }
        
        @Override
        public int getSentCount() {
            return this.sentCount.get();
        }
        
        @Override
        public long getSentUncompressedTotal() {
            return this.sentUncompressedTotal.get();
        }
        
        @Override
        public long getSentCompressedTotal() {
            return this.sentCompressedTotal.get();
        }
        
        @Override
        public long getSentUncompressedMin() {
            return (this.sentCount.get() > 0) ? this.sentUncompressedMin.get() : 0L;
        }
        
        @Override
        public long getSentUncompressedMax() {
            return this.sentUncompressedMax.get();
        }
        
        @Override
        public long getSentCompressedMin() {
            return (this.sentCount.get() > 0) ? this.sentCompressedMin.get() : 0L;
        }
        
        @Override
        public long getSentCompressedMax() {
            return this.sentCompressedMax.get();
        }
        
        @Override
        public double getSentUncompressedAvg() {
            return this.sentUncompressedAvg.get();
        }
        
        @Override
        public double getSentCompressedAvg() {
            return this.sentCompressedAvg.get();
        }
        
        @Override
        public int getReceivedCount() {
            return this.receivedCount.get();
        }
        
        @Override
        public long getReceivedUncompressedTotal() {
            return this.receivedUncompressedTotal.get();
        }
        
        @Override
        public long getReceivedCompressedTotal() {
            return this.receivedCompressedTotal.get();
        }
        
        @Override
        public long getReceivedUncompressedMin() {
            return (this.receivedCount.get() > 0) ? this.receivedUncompressedMin.get() : 0L;
        }
        
        @Override
        public long getReceivedUncompressedMax() {
            return this.receivedUncompressedMax.get();
        }
        
        @Override
        public long getReceivedCompressedMin() {
            return (this.receivedCount.get() > 0) ? this.receivedCompressedMin.get() : 0L;
        }
        
        @Override
        public long getReceivedCompressedMax() {
            return this.receivedCompressedMax.get();
        }
        
        @Override
        public double getReceivedUncompressedAvg() {
            return this.receivedUncompressedAvg.get();
        }
        
        @Override
        public double getReceivedCompressedAvg() {
            return this.receivedCompressedAvg.get();
        }
        
        @Nonnull
        @Override
        public RecentStats getSentRecently() {
            return this.computeRecentStats(this.sentRecently);
        }
        
        @Nonnull
        @Override
        public RecentStats getReceivedRecently() {
            return this.computeRecentStats(this.receivedRecently);
        }
        
        private RecentStats computeRecentStats(final Queue<SizeRecord> queue) {
            int count = 0;
            long uncompressedTotal = 0L;
            long compressedTotal = 0L;
            int uncompressedMin = Integer.MAX_VALUE;
            int uncompressedMax = 0;
            int compressedMin = Integer.MAX_VALUE;
            int compressedMax = 0;
            for (final SizeRecord record : queue) {
                ++count;
                uncompressedTotal += record.uncompressedSize;
                compressedTotal += record.compressedSize;
                uncompressedMin = Math.min(uncompressedMin, record.uncompressedSize);
                uncompressedMax = Math.max(uncompressedMax, record.uncompressedSize);
                compressedMin = Math.min(compressedMin, record.compressedSize);
                compressedMax = Math.max(compressedMax, record.compressedSize);
            }
            if (count == 0) {
                return RecentStats.EMPTY;
            }
            return new RecentStats(count, uncompressedTotal, compressedTotal, uncompressedMin, uncompressedMax, compressedMin, compressedMax);
        }
        
        public void reset() {
            this.sentCount.set(0);
            this.sentUncompressedTotal.set(0L);
            this.sentCompressedTotal.set(0L);
            this.sentUncompressedMin.set(Long.MAX_VALUE);
            this.sentUncompressedMax.set(0L);
            this.sentCompressedMin.set(Long.MAX_VALUE);
            this.sentCompressedMax.set(0L);
            this.sentUncompressedAvg.clear();
            this.sentCompressedAvg.clear();
            this.sentRecently.clear();
            this.receivedCount.set(0);
            this.receivedUncompressedTotal.set(0L);
            this.receivedCompressedTotal.set(0L);
            this.receivedUncompressedMin.set(Long.MAX_VALUE);
            this.receivedUncompressedMax.set(0L);
            this.receivedCompressedMin.set(Long.MAX_VALUE);
            this.receivedCompressedMax.set(0L);
            this.receivedUncompressedAvg.clear();
            this.receivedCompressedAvg.clear();
            this.receivedRecently.clear();
        }
        
        static {
            METRICS_REGISTRY = new MetricsRegistry<PacketStatsEntry>().register("PacketId", PacketStatsEntry::getPacketId, Codec.INTEGER).register("Name", PacketStatsEntry::getName, Codec.STRING).register("SentCount", PacketStatsEntry::getSentCount, Codec.INTEGER).register("SentUncompressedTotal", PacketStatsEntry::getSentUncompressedTotal, Codec.LONG).register("SentCompressedTotal", PacketStatsEntry::getSentCompressedTotal, Codec.LONG).register("SentUncompressedMin", PacketStatsEntry::getSentUncompressedMin, Codec.LONG).register("SentUncompressedMax", PacketStatsEntry::getSentUncompressedMax, Codec.LONG).register("SentCompressedMin", PacketStatsEntry::getSentCompressedMin, Codec.LONG).register("SentCompressedMax", PacketStatsEntry::getSentCompressedMax, Codec.LONG).register("ReceivedCount", PacketStatsEntry::getReceivedCount, Codec.INTEGER).register("ReceivedUncompressedTotal", PacketStatsEntry::getReceivedUncompressedTotal, Codec.LONG).register("ReceivedCompressedTotal", PacketStatsEntry::getReceivedCompressedTotal, Codec.LONG).register("ReceivedUncompressedMin", PacketStatsEntry::getReceivedUncompressedMin, Codec.LONG).register("ReceivedUncompressedMax", PacketStatsEntry::getReceivedUncompressedMax, Codec.LONG).register("ReceivedCompressedMin", PacketStatsEntry::getReceivedCompressedMin, Codec.LONG).register("ReceivedCompressedMax", PacketStatsEntry::getReceivedCompressedMax, Codec.LONG);
        }
        
        record SizeRecord(long nanos, int uncompressedSize, int compressedSize) {}
    }
}
