package com.capgemini.framework.common;

/** Formats a double to String, allowing specification of a number of <i>significant</i> digits. */
public final class DoubleFormatter {

    private static final String MINUS = "-";
    private static final String EMPTY_STRING = "";
    private static final String DOT = ".";
    private static final char ZERO_CHAR = '0';
    private static final int TEN = 10;
    /** Limited by the java Long used to represent the mantissa. */
    private static final int MAX_SIGNIFICANT_DIGITS = 16;

    private DoubleFormatter() {
        // nothing in here
    }

    /**
     * Rounds a double value to a number of <i>significant</i> digits.
     * <p>
     * Examples:
     * <ul>
     * <li>{@code toSignificantDigits(20001054, 9)} gives {@code "20001054"}</li>
     * <li>{@code toSignificantDigits(-20001054, 9)} gives {@code "-20001054"}</li>
     * <li>{@code toSignificantDigits(200010.54, 9)} gives {@code "200010.54"}</li>
     * <li>{@code toSignificantDigits(200010.54, 8)} gives {@code "200010.54"}</li>
     * <li>{@code toSignificantDigits(200010.54, 3)} gives {@code "200000"}</li>
     * <li>{@code toSignificantDigits(200010.54, 7)} gives {@code "200010.5"}</li>
     * <li>{@code toSignificantDigits(200010.54, 7)} gives {@code "200010.6"}</li>
     * <li>{@code toSignificantDigits(0.00020051024, 6)} gives {@code "0.00020051"}</li>
     * <li>{@code toSignificantDigits(-0.00020051054, 3)} gives {@code "-0.000201"}</li>
     * </ul>
     * 
     * @param value the value to be rounded
     * @param someSignificantDigits MUST be lower or equal to 16 (limited by the java Long used to represent the mantissa)
     * @return the {@link String} representation of the value, with the required digits
     */
    public static String toSignificantDigits(final double value, final int someSignificantDigits) {
        if (value == 0) {
            return "0";
        }
        int significantDigits = Math.min(someSignificantDigits, MAX_SIGNIFICANT_DIGITS);

        // Extract sub-components: sign, mantissa and exponent
        double abs = Math.abs(value);
        int power = (int) Math.floor(Math.log10(abs));
        double mantissa = abs * Math.pow(TEN, -power);
        String mantissaStr = Double.toString(mantissa);

        // Exctract the coma position
        int numDigits;
        String mantissaWithoutDot;
        if (mantissaStr.contains(DOT)) {
            numDigits = mantissaStr.length() - 1;
            mantissaWithoutDot = mantissaStr.replace(DOT, EMPTY_STRING);
        }
        else {
            numDigits = 1;
            mantissaWithoutDot = mantissaStr;
        }

        // Mantissa cannot exceed 2^63 (about 10^18) which is max value for long
        if (mantissaWithoutDot.length() > MAX_SIGNIFICANT_DIGITS) {
            mantissaWithoutDot = mantissaWithoutDot.substring(0, MAX_SIGNIFICANT_DIGITS);
            numDigits = MAX_SIGNIFICANT_DIGITS;
        }

        // Round to the number of significant digits IF there are more digits than significant digits
        String truncatedMantissaWithoutDot;
        if (numDigits > significantDigits) {
            long trueValue = Long.valueOf(mantissaWithoutDot);
            double powToRound = (long) Math.pow(TEN, mantissaWithoutDot.length() - significantDigits);
            long roundedFinalNumber = Math.round(trueValue / powToRound) * (long) powToRound;
            truncatedMantissaWithoutDot = String.valueOf(roundedFinalNumber);
        }
        else { // No need to round
            truncatedMantissaWithoutDot = mantissaWithoutDot;
        }
        // Remove uneeded zeroes in the mantissa
        while (truncatedMantissaWithoutDot.length() > 1 && truncatedMantissaWithoutDot.charAt(truncatedMantissaWithoutDot.length() - 1) == ZERO_CHAR) {
            truncatedMantissaWithoutDot = truncatedMantissaWithoutDot.substring(0, truncatedMantissaWithoutDot.length() - 1);
        }

        return compileTheNumber(value, power, truncatedMantissaWithoutDot);
    }

    private static String compileTheNumber(final double value, final int power, final String truncatedMantissaWithoutDot) {
        // Write the number using its sign, mantissa and power
        String signum = Math.signum(value) >= 0 ? EMPTY_STRING : MINUS;
        if (power >= 0) {
            if (power < truncatedMantissaWithoutDot.length() - 1) {
                // fractional number 'ABC.XYZ'
                String intPart = truncatedMantissaWithoutDot.substring(0, power + 1);
                String fracPart = truncatedMantissaWithoutDot.substring(power + 1);
                return signum + intPart + DOT + fracPart;
            }
            else {
                // Integer number (significant mantisse is shorter than power) of the form 'ABCXYZ000'
                StringBuilder builder = new StringBuilder(signum).append(truncatedMantissaWithoutDot);
                for (int additionalZeroes = 0; additionalZeroes < power + 1 - truncatedMantissaWithoutDot.length(); additionalZeroes++) {
                    builder.append(ZERO_CHAR);
                }
                return builder.toString();
            }
        }
        else {
            // Leading zeroes, then full significant mantissa: '0.000ABCXYZ'
            StringBuilder builder = new StringBuilder(signum).append("0.");
            for (int additionalZeroes = 0; additionalZeroes < -power - 1; additionalZeroes++) {
                builder.append(ZERO_CHAR);
            }
            builder.append(truncatedMantissaWithoutDot);
            return builder.toString();
        }
    }
}
