package com.atlassian.bitbucket.util;

import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.List;

public class NumberUtils {

    private static final List<SizeUnit> SIZE_UNITS = ImmutableList.copyOf(SizeUnit.values());

    private static final Logger log = LoggerFactory.getLogger(NumberUtils.class);

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

    /**
     * @param s the string to parse
     * @param defaultValue the value to return if {@code s} cannot be parsed
     * @param message the error message to log if parsing fails
     * @return parsed {@code Integer} value from {@code s}, or {@code defaultValue} if a parsing error occurred
     */
    @Nullable
    public static Integer parse(@Nullable String s, @Nullable Integer defaultValue, @Nullable String message) {
        if (StringUtils.isNotEmpty(s)) {
            try {
                return Integer.parseInt(s);
            } catch (NumberFormatException e) {
                if (message != null) {
                    log.error(message);
                }
            }
        }

        return defaultValue;
    }

    /**
     * @param s the string to parse
     * @param message the error message to log if parsing fails
     * @return parsed {@code Integer} value from {@code s}, or {@code null} if a parsing error occurred
     */
    @Nullable
    public static Integer parse(@Nullable String s, @Nullable String message) {
        return parse(s, null, message);
    }

    /**
     * @param size the of a 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);
    }

    /**
     * @param s the string to parse
     * @param defaultValue the value to return if {@code s} cannot be parsed
     * @return parsed {@code Long} value from {@code s}, or {@code defaultValue} if a parsing error occurred
     */
    @Nullable
    public static Long toLong(@Nullable String s, @Nullable Long defaultValue) {
        return toLong(s, defaultValue, null);
    }

    /**
     * @param s the string to parse
     * @param defaultValue the value to return if {@code s} cannot be parsed
     * @param message the error message to log if parsing fails
     * @return parsed {@code Long} value from {@code s}, or {@code defaultValue} if a parsing error occurred
     */
    @Nullable
    public static Long toLong(@Nullable String s, @Nullable Long defaultValue, @Nullable String message) {
        if (StringUtils.isNotEmpty(s)) {
            try {
                return Long.parseLong(s);
            } catch (NumberFormatException e) {
                if (message != null) {
                    log.error(message);
                }
            }
        }

        return defaultValue;
    }

    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());
        }
    }
}
