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

package com.hypixel.hytale.metrics.metric;

import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
import java.util.function.Function;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import java.util.concurrent.TimeUnit;
import java.util.Arrays;
import com.hypixel.hytale.math.util.MathUtil;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.Codec;

public class HistoricMetric
{
    public static final HistoricMetric[] EMPTY_ARRAY;
    public static final Codec<HistoricMetric> METRICS_CODEC;
    private final long[] periodsNanos;
    @Nonnull
    private final AverageCollector[] periodAverages;
    @Nonnull
    private final int[] startIndices;
    private final int bufferSize;
    @Nonnull
    private final long[] timestamps;
    @Nonnull
    private final long[] values;
    int nextIndex;
    
    private HistoricMetric() {
        throw new UnsupportedOperationException("Not supported");
    }
    
    private HistoricMetric(@Nonnull final Builder builder) {
        this.periodsNanos = builder.periods.toLongArray();
        this.periodAverages = new AverageCollector[this.periodsNanos.length];
        for (int i = 0; i < this.periodAverages.length; ++i) {
            this.periodAverages[i] = new AverageCollector();
        }
        this.startIndices = new int[this.periodsNanos.length];
        long longestPeriod = 0L;
        for (final long period : this.periodsNanos) {
            if (period > longestPeriod) {
                longestPeriod = period;
            }
        }
        this.bufferSize = (int)MathUtil.fastCeil(longestPeriod / (double)builder.minimumInterval);
        this.timestamps = new long[this.bufferSize];
        this.values = new long[this.bufferSize];
        Arrays.fill(this.timestamps, Long.MAX_VALUE);
    }
    
    public long[] getPeriodsNanos() {
        return this.periodsNanos;
    }
    
    public long calculateMin(final int periodIndex) {
        final int bufferSize = this.bufferSize;
        final long[] values = this.values;
        final int start = this.startIndices[periodIndex];
        final int nextIndex = this.nextIndex;
        long min = Long.MAX_VALUE;
        if (start < nextIndex) {
            for (int i = start; i < nextIndex; ++i) {
                final long value = values[i];
                if (value < min) {
                    min = value;
                }
            }
        }
        else {
            for (int i = start; i < bufferSize; ++i) {
                final long value = values[i];
                if (value < min) {
                    min = value;
                }
            }
            for (int i = 0; i < nextIndex; ++i) {
                final long value = values[i];
                if (value < min) {
                    min = value;
                }
            }
        }
        return min;
    }
    
    public double getAverage(final int periodIndex) {
        return this.periodAverages[periodIndex].get();
    }
    
    public long calculateMax(final int periodIndex) {
        final int bufferSize = this.bufferSize;
        final long[] values = this.values;
        final int start = this.startIndices[periodIndex];
        final int nextIndex = this.nextIndex;
        long max = Long.MIN_VALUE;
        if (start < nextIndex) {
            for (int i = start; i < nextIndex; ++i) {
                final long value = values[i];
                if (value > max) {
                    max = value;
                }
            }
        }
        else {
            for (int i = start; i < bufferSize; ++i) {
                final long value = values[i];
                if (value > max) {
                    max = value;
                }
            }
            for (int i = 0; i < nextIndex; ++i) {
                final long value = values[i];
                if (value > max) {
                    max = value;
                }
            }
        }
        return max;
    }
    
    public void clear() {
        for (final AverageCollector average : this.periodAverages) {
            average.clear();
        }
        Arrays.fill(this.startIndices, 0);
        Arrays.fill(this.timestamps, Long.MAX_VALUE);
        Arrays.fill(this.values, 0L);
        this.nextIndex = 0;
    }
    
    public void add(final long timestampNanos, final long value) {
        final long[] periodsNanos = this.periodsNanos;
        final AverageCollector[] periodAverages = this.periodAverages;
        final int[] startIndices = this.startIndices;
        final int bufferSize = this.bufferSize;
        final long[] timestamps = this.timestamps;
        final long[] values = this.values;
        final int nextIndex = this.nextIndex;
        for (int periodLength = periodsNanos.length, i = 0; i < periodLength; ++i) {
            final long oldestPossibleTimestamp = timestampNanos - periodsNanos[i];
            final AverageCollector average = periodAverages[i];
            int start = startIndices[i];
            while (timestamps[start] < oldestPossibleTimestamp) {
                final long oldValue = values[start];
                average.remove((double)oldValue);
                start = (start + 1) % bufferSize;
                if (start == nextIndex) {
                    break;
                }
            }
            startIndices[i] = start;
            average.add((double)value);
        }
        timestamps[nextIndex] = timestampNanos;
        values[nextIndex] = value;
        this.nextIndex = (nextIndex + 1) % bufferSize;
    }
    
    public long[] getTimestamps(final int periodIndex) {
        final int start = this.startIndices[periodIndex];
        final long[] timestamps = this.timestamps;
        final int nextIndex = this.nextIndex;
        if (start < nextIndex) {
            return Arrays.copyOfRange(timestamps, start, nextIndex);
        }
        final int length = timestamps.length - start;
        final long[] data = new long[length + nextIndex];
        System.arraycopy(timestamps, start, data, 0, length);
        System.arraycopy(timestamps, 0, data, length, nextIndex);
        return data;
    }
    
    public long[] getValues(final int periodIndex) {
        final int start = this.startIndices[periodIndex];
        final long[] values = this.values;
        final int nextIndex = this.nextIndex;
        if (start < nextIndex) {
            return Arrays.copyOfRange(values, start, nextIndex);
        }
        final int length = this.bufferSize - start;
        final long[] data = new long[length + nextIndex];
        System.arraycopy(values, start, data, 0, length);
        System.arraycopy(values, 0, data, length, nextIndex);
        return data;
    }
    
    public long[] getAllTimestamps() {
        return this.getTimestamps(this.periodsNanos.length - 1);
    }
    
    public long[] getAllValues() {
        return this.getValues(this.periodsNanos.length - 1);
    }
    
    public void setAllTimestamps(@Nonnull final long[] timestamps) {
        final int length = timestamps.length;
        System.arraycopy(timestamps, 0, this.timestamps, 0, length);
        int periodIndex = 0;
        final long last = timestamps[length - 1];
        for (int i = length - 2; i >= 0; --i) {
            if (last - timestamps[i] >= this.periodsNanos[periodIndex]) {
                this.startIndices[periodIndex] = i + 1;
                if (++periodIndex >= this.periodsNanos.length) {
                    break;
                }
            }
        }
        if (periodIndex < this.periodsNanos.length) {
            while (periodIndex < this.periodsNanos.length) {
                this.periodsNanos[periodIndex] = 0L;
                ++periodIndex;
            }
        }
        this.nextIndex = length;
    }
    
    public void setAllValues(@Nonnull final long[] values) {
        System.arraycopy(values, 0, this.values, 0, values.length);
    }
    
    public long getLastValue() {
        if (this.nextIndex == 0) {
            return this.values[this.bufferSize - 1];
        }
        return this.values[this.nextIndex - 1];
    }
    
    @Nonnull
    public static Builder builder(final long minimumInterval, @Nonnull final TimeUnit unit) {
        return new Builder(minimumInterval, unit);
    }
    
    static {
        EMPTY_ARRAY = new HistoricMetric[0];
        METRICS_CODEC = BuilderCodec.builder(HistoricMetric.class, HistoricMetric::new).append(new KeyedCodec<long[]>("PeriodsNanos", Codec.LONG_ARRAY), (historicMetric, s) -> {
            throw new UnsupportedOperationException("Not supported");
        }, historicMetric -> historicMetric.periodsNanos).add().append(new KeyedCodec("Timestamps", Codec.LONG_ARRAY), (historicMetric, s) -> {
            throw new UnsupportedOperationException("Not supported");
        }, HistoricMetric::getAllTimestamps).add().append(new KeyedCodec("Values", Codec.LONG_ARRAY), (historicMetric, s) -> {
            throw new UnsupportedOperationException("Not supported");
        }, HistoricMetric::getAllValues).add().build();
    }
    
    public static class Builder
    {
        private final long minimumInterval;
        private final LongList periods;
        
        private Builder(final long minimumInterval, @Nonnull final TimeUnit unit) {
            this.periods = new LongArrayList();
            this.minimumInterval = unit.toNanos(minimumInterval);
        }
        
        @Nonnull
        public Builder addPeriod(final long period, @Nonnull final TimeUnit unit) {
            final long nanos = unit.toNanos(period);
            for (int i = 0; i < this.periods.size(); ++i) {
                if (this.periods.getLong(i) > nanos) {
                    throw new IllegalArgumentException("Period's must be increasing in length");
                }
            }
            this.periods.add(nanos);
            return this;
        }
        
        @Nonnull
        public HistoricMetric build() {
            return new HistoricMetric(this);
        }
    }
}
