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

package com.hypixel.hytale.common.util;

import java.util.Formatter;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.DoubleUnaryOperator;
import com.hypixel.hytale.metrics.metric.Metric;
import javax.annotation.Nonnull;
import java.util.concurrent.TimeUnit;
import java.util.EnumMap;

public class FormatUtil
{
    private static final String[] NUMBER_SUFFIXES;
    private static final EnumMap<TimeUnit, String> timeUnitToShortString;
    public static final long DAY_AS_NANOS;
    public static final long HOUR_AS_NANOS;
    public static final long MINUTE_AS_NANOS;
    public static final long SECOND_AS_NANOS;
    public static final long MILLISECOND_AS_NANOS;
    public static final long MICOSECOND_AS_NANOS;
    
    @Nonnull
    public static TimeUnit largestUnit(final long value, @Nonnull final TimeUnit unit) {
        final long nanos = unit.toNanos(value);
        if (nanos > FormatUtil.DAY_AS_NANOS) {
            return TimeUnit.DAYS;
        }
        if (nanos > FormatUtil.HOUR_AS_NANOS) {
            return TimeUnit.HOURS;
        }
        if (nanos > FormatUtil.MINUTE_AS_NANOS) {
            return TimeUnit.MINUTES;
        }
        if (nanos > FormatUtil.SECOND_AS_NANOS) {
            return TimeUnit.SECONDS;
        }
        if (nanos > FormatUtil.MILLISECOND_AS_NANOS) {
            return TimeUnit.MILLISECONDS;
        }
        if (nanos > FormatUtil.MICOSECOND_AS_NANOS) {
            return TimeUnit.MICROSECONDS;
        }
        return TimeUnit.NANOSECONDS;
    }
    
    @Nonnull
    public static String simpleTimeUnitFormat(@Nonnull final Metric metric, @Nonnull final TimeUnit timeUnit, final int rounding) {
        final TimeUnit largestUnit = largestUnit(Math.round(metric.getAverage()), timeUnit);
        return simpleTimeUnitFormat(metric, timeUnit, largestUnit, rounding);
    }
    
    @Nonnull
    public static String simpleTimeUnitFormat(@Nonnull final Metric metric, final TimeUnit timeUnit, @Nonnull final TimeUnit largestUnit, final int rounding) {
        final long min = metric.getMin();
        final double average = metric.getAverage();
        final long max = metric.getMax();
        return simpleTimeUnitFormat(min, average, max, timeUnit, largestUnit, rounding);
    }
    
    @Nonnull
    public static String simpleTimeUnitFormat(final long min, final double average, final long max, final TimeUnit timeUnit, @Nonnull final TimeUnit largestUnit, final int rounding) {
        final int roundValue = (int)Math.pow(10.0, rounding);
        final long range = Math.round(Math.max(Math.abs(average - min), Math.abs(max - average)));
        final long averageNanos = largestUnit.convert(Math.round(average * roundValue), timeUnit);
        final long rangeNanos = largestUnit.convert(range * roundValue, timeUnit);
        final String unitStr = FormatUtil.timeUnitToShortString.get(largestUnit);
        return averageNanos / (double)roundValue + unitStr + "  +/-" + rangeNanos / (double)roundValue + unitStr;
    }
    
    @Nonnull
    public static String simpleTimeUnitFormat(final long value, @Nonnull final TimeUnit timeUnit, final int rounding) {
        final int roundValue = (int)Math.pow(10.0, rounding);
        final TimeUnit largestUnit = largestUnit(value, timeUnit);
        final long averageNanos = largestUnit.convert(value * roundValue, timeUnit);
        final String unitStr = FormatUtil.timeUnitToShortString.get(largestUnit);
        return averageNanos / (double)roundValue + unitStr;
    }
    
    @Nonnull
    public static String simpleFormat(final long min1, final double average1, final long max1, @Nonnull final DoubleUnaryOperator doubleFunction, final int rounding) {
        final double average2 = doubleFunction.applyAsDouble(average1);
        final double min2 = Math.abs(average2 - doubleFunction.applyAsDouble((double)min1));
        final double max2 = Math.abs(doubleFunction.applyAsDouble((double)max1) - average2);
        final double range = Math.max(min2, max2);
        return simpleFormat(rounding, average2, range);
    }
    
    @Nonnull
    public static String simpleFormat(@Nonnull final Metric metric) {
        return simpleFormat(metric, 2);
    }
    
    @Nonnull
    public static String simpleFormat(@Nonnull final Metric metric, final int rounding) {
        final double average = metric.getAverage();
        final double min = Math.abs(average - metric.getMin());
        final double max = Math.abs(metric.getMax() - average);
        final double range = Math.max(min, max);
        return simpleFormat(rounding, average, range);
    }
    
    @Nonnull
    public static String simpleFormat(final int rounding, final double average, final double range) {
        final int roundValue = (int)Math.pow(10.0, rounding);
        return (long)(average * roundValue) / (double)roundValue + "  +/-" + (long)(range * roundValue) / (double)roundValue;
    }
    
    @Nonnull
    public static String timeUnitToString(@Nonnull final Metric metric, @Nonnull final TimeUnit timeUnit) {
        final double min = Math.abs(metric.getAverage() - metric.getMin());
        final double max = Math.abs(metric.getMax() - metric.getAverage());
        final long range = Math.round(Math.max(min, max));
        return timeUnitToString(Math.round(metric.getAverage()), timeUnit) + "  +/-" + timeUnitToString(range, timeUnit);
    }
    
    @Nonnull
    public static String timeUnitToString(final long value, @Nonnull final TimeUnit timeUnit) {
        return timeUnitToString(value, timeUnit, false);
    }
    
    @Nonnull
    public static String timeUnitToString(final long value, @Nonnull final TimeUnit timeUnit, final boolean paddingBetween) {
        boolean p = false;
        final StringBuilder sb = new StringBuilder();
        final AtomicLong time = new AtomicLong(value);
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.DAYS, "days", false, paddingBetween);
        final boolean hasHours = timeToStringPart(time, sb, p, timeUnit, TimeUnit.HOURS, ":", true, paddingBetween);
        p |= hasHours;
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MINUTES, hasHours ? ":" : "min", false, paddingBetween);
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.SECONDS, hasHours ? "" : "sec", !hasHours, paddingBetween);
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MILLISECONDS, "ms", true, paddingBetween);
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.MICROSECONDS, "us", true, paddingBetween);
        p |= timeToStringPart(time, sb, p, timeUnit, TimeUnit.NANOSECONDS, "ns", true, paddingBetween);
        return sb.toString();
    }
    
    @Nonnull
    public static String nanosToString(final long nanos) {
        return timeUnitToString(nanos, TimeUnit.NANOSECONDS);
    }
    
    private static boolean timeToStringPart(@Nonnull final AtomicLong time, @Nonnull final StringBuilder sb, final boolean previous, @Nonnull final TimeUnit timeUnitFrom, @Nonnull final TimeUnit timeUnitTo, final String after, final boolean paddingBefore, final boolean paddingBetween) {
        if (timeUnitFrom.ordinal() > timeUnitTo.ordinal()) {
            return false;
        }
        final long timeInUnitTo = timeUnitTo.convert(time.get(), timeUnitFrom);
        time.getAndAdd(-timeUnitFrom.convert(timeInUnitTo, timeUnitTo));
        if (timeInUnitTo > 0L || (previous && time.get() > 0L) || (!previous && timeUnitFrom == timeUnitTo)) {
            if (paddingBefore && previous) {
                sb.append(' ');
            }
            sb.append(timeInUnitTo);
            if (paddingBetween) {
                sb.append(' ');
            }
            sb.append(after);
            return true;
        }
        return false;
    }
    
    @Nonnull
    public static String bytesToString(final long bytes) {
        return bytesToString(bytes, false);
    }
    
    @Nonnull
    public static String bytesToString(final long bytes, final boolean si) {
        final int unit = si ? 1000 : 1024;
        if (bytes < unit) {
            return bytes + " B";
        }
        final int exp = (int)(Math.log((double)bytes) / Math.log(unit));
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"));
    }
    
    @Nonnull
    public static String addNumberSuffix(final int i) {
        return switch (i % 100) {
            case 11,  12,  13 -> i + "th";
            default -> i + FormatUtil.NUMBER_SUFFIXES[i % 10];
        };
    }
    
    public static void formatArray(@Nonnull final Formatter formatter, @Nonnull final String format, @Nonnull final Object[] args) {
        for (final Object arg : args) {
            formatter.format(format, arg);
        }
    }
    
    public static void formatArgs(@Nonnull final Formatter formatter, @Nonnull final String format, @Nonnull final Object... args) {
        formatArray(formatter, format, args);
    }
    
    static {
        NUMBER_SUFFIXES = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
        timeUnitToShortString = new EnumMap<TimeUnit, String>() {
            {
                this.put(TimeUnit.DAYS, "days");
                this.put(TimeUnit.HOURS, "hours");
                this.put(TimeUnit.MINUTES, "min");
                this.put(TimeUnit.SECONDS, "s");
                this.put(TimeUnit.MILLISECONDS, "ms");
                this.put(TimeUnit.MICROSECONDS, "us");
                this.put(TimeUnit.NANOSECONDS, "ns");
            }
        };
        DAY_AS_NANOS = TimeUnit.DAYS.toNanos(1L);
        HOUR_AS_NANOS = TimeUnit.HOURS.toNanos(1L);
        MINUTE_AS_NANOS = TimeUnit.MINUTES.toNanos(1L);
        SECOND_AS_NANOS = TimeUnit.SECONDS.toNanos(1L);
        MILLISECOND_AS_NANOS = TimeUnit.MILLISECONDS.toNanos(1L);
        MICOSECOND_AS_NANOS = TimeUnit.MICROSECONDS.toNanos(1L);
    }
}
