/*
 * Copyright 2016 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sejda.sambox.util;

/**
 * This class contains methods to format numbers.
 *
 * @author Michael Doswald
 */
public final class NumberFormatUtil
{

    private NumberFormatUtil()
    {
        //
    }

    /**
     * Maximum number of fraction digits supported by the format methods
     */
    private static final int MAX_FRACTION_DIGITS = 5;

    /**
     * Contains the power of ten values for fast lookup in the format methods
     */
    private static final long[] POWER_OF_TENS;
    private static final int[] POWER_OF_TENS_INT;

    static
    {
        POWER_OF_TENS = new long[19];
        POWER_OF_TENS[0] = 1;

        for (int exp = 1; exp < POWER_OF_TENS.length; exp++)
        {
            POWER_OF_TENS[exp] = POWER_OF_TENS[exp - 1] * 10;
        }

        POWER_OF_TENS_INT = new int[10];
        POWER_OF_TENS_INT[0] = 1;

        for (int exp = 1; exp < POWER_OF_TENS_INT.length; exp++)
        {
            POWER_OF_TENS_INT[exp] = POWER_OF_TENS_INT[exp - 1] * 10;
        }
    }

    /**
     * Fast variant to format a floating point value to a ASCII-string. The format will fail if the value is greater
     * than {@link Long#MAX_VALUE}, smaller or equal to {@link Long#MIN_VALUE}, is {@link Float#NaN}, infinite or the
     * number of requested fraction digits is greater than {@link #MAX_FRACTION_DIGITS}.
     * 
     * When the number contains more fractional digits than {@code maxFractionDigits} the value will be rounded.
     * Rounding is done to the nearest possible value, with the tie breaking rule of rounding away from zero.
     * 
     * @param value The float value to format
     * @param maxFractionDigits The maximum number of fraction digits used
     * @param asciiBuffer The output buffer to write the formatted value to
     *
     * @return The number of bytes used in the buffer or {@code -1} if formatting failed
     */
    public static int formatFloatFast(float value, int maxFractionDigits, byte[] asciiBuffer)
    {
        if (Float.isNaN(value) || Float.isInfinite(value) || value > Long.MAX_VALUE
                || value <= Long.MIN_VALUE || maxFractionDigits > MAX_FRACTION_DIGITS)
        {
            return -1;
        }

        int offset = 0;
        long integerPart = (long) value;

        // handle sign
        if (value < 0)
        {
            asciiBuffer[offset++] = '-';
            integerPart = -integerPart;
        }

        // extract fraction part
        long fractionPart = (long) ((Math.abs((double) value) - integerPart)
                * POWER_OF_TENS[maxFractionDigits] + 0.5d);

        // Check for rounding to next integer
        if (fractionPart >= POWER_OF_TENS[maxFractionDigits])
        {
            integerPart++;
            fractionPart -= POWER_OF_TENS[maxFractionDigits];
        }

        // format integer part
        offset = formatPositiveNumber(integerPart, getExponent(integerPart), false, asciiBuffer,
                offset);

        if (fractionPart > 0 && maxFractionDigits > 0)
        {
            asciiBuffer[offset++] = '.';
            offset = formatPositiveNumber(fractionPart, maxFractionDigits - 1, true, asciiBuffer,
                    offset);
        }

        return offset;
    }

    /**
     * Formats a positive integer number starting with the digit at {@code 10^exp}.
     *
     * @param number The number to format
     * @param exp The start digit
     * @param omitTrailingZeros Whether the formatting should stop if only trailing zeros are left. This is needed e.g.
     * when formatting fractions of a number.
     * @param asciiBuffer The buffer to write the ASCII digits to
     * @param startOffset The start offset into the buffer to start writing
     *
     * @return The offset into the buffer which contains the first byte that was not filled by the method
     */
    private static int formatPositiveNumber(long number, int exp, boolean omitTrailingZeros,
            byte[] asciiBuffer, int startOffset)
    {
        int offset = startOffset;
        long remaining = number;

        while (remaining > Integer.MAX_VALUE && (!omitTrailingZeros || remaining > 0))
        {
            long digit = remaining / POWER_OF_TENS[exp];
            remaining -= (digit * POWER_OF_TENS[exp]);

            asciiBuffer[offset++] = (byte) ('0' + digit);
            exp--;
        }

        // If the remaining fits into an integer, use int arithmetic as it is faster
        int remainingInt = (int) remaining;
        while (exp >= 0 && (!omitTrailingZeros || remainingInt > 0))
        {
            int digit = remainingInt / POWER_OF_TENS_INT[exp];
            remainingInt -= (digit * POWER_OF_TENS_INT[exp]);

            asciiBuffer[offset++] = (byte) ('0' + digit);
            exp--;
        }

        return offset;
    }

    /**
     * Returns the highest exponent of 10 where {@code 10^exp < number} for numbers > 0
     */
    private static int getExponent(long number)
    {
        for (int exp = 0; exp < (POWER_OF_TENS.length - 1); exp++)
        {
            if (number < POWER_OF_TENS[exp + 1])
            {
                return exp;
            }
        }

        return POWER_OF_TENS.length - 1;
    }

}
