package com.atlassian.bitbucket.mesh.util;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import javax.annotation.Nonnull;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static java.util.Objects.requireNonNull;

public class NumberUtils {

    private static final List<SizeUnit> SIZE_UNITS = ImmutableList.copyOf(SizeUnit.values());
    private static final Map<TimeUnit, String> TIME_UNITS = ImmutableMap.of(
            TimeUnit.DAYS, "d",
            TimeUnit.HOURS, "h",
            TimeUnit.MINUTES, "m",
            TimeUnit.SECONDS, "s",
            TimeUnit.MILLISECONDS, "ms");

    private NumberUtils() {
        throw new UnsupportedOperationException(getClass().getName() +
                                                " is a utility class and should not be instantiated");
    }

    /**
     * Formats duration as a human readable string such as {@code 4d8h23m}. Only the 3 most significant units
     * are returned.
     *
     * @param duration the duration to format
     * @return the human readable version of the duration
     */
    @Nonnull
    public static String formatDuration(@Nonnull Duration duration) {
        long remainderMs = requireNonNull(duration, "duration").toMillis();
        StringBuilder builder = new StringBuilder();
        int unitsProcessed = 0;

        for (Map.Entry<TimeUnit, String> entry : TIME_UNITS.entrySet()) {
            TimeUnit unit = entry.getKey();
            long value = unit.convert(remainderMs, TimeUnit.MILLISECONDS);
            if (value > 0) {
                builder.append(value).append(entry.getValue());
                remainderMs -= TimeUnit.MILLISECONDS.convert(value, unit);
            }
            if (value > 0 || builder.length() > 0) {
                unitsProcessed++;
                if (unitsProcessed == 3) {
                    //only include the 3 most significant units
                    break;
                }
            }
        }
        if (builder.length() == 0) {
            return "0ms";
        }
        return builder.toString();
    }

    /**
     * @param size the size of something in bytes
     * @return the human readable version of the bytes
     */
    @Nonnull
    public static String formatSize(double size) {
        Iterator<SizeUnit> it = SIZE_UNITS.iterator();
        SizeUnit unit = it.next();
        while (it.hasNext() && size > 1024D) {
            size /= 1024D;
            unit = it.next();
        }
        return unit.format(size);
    }

    private enum SizeUnit {

        B {
            @Override
            public String format(double size) {
                //For bytes no fractional part should be displayed; it's the smallest unit of measurement, so
                //it will always be represented as a whole number
                return String.format("%1$.0f B", size);
            }
        },
        KB,
        MB,
        GB;

        public String format(double size) {
            //For all other types, output 2 decimal places (e.g. 159.85 MB)
            return String.format("%1$.2f %2$s", size, name());
        }
    }
}
