package org.solovyev.common;

import org.junit.Before;
import org.junit.Test;

import java.math.BigInteger;

import static java.lang.Math.pow;
import static java.math.BigInteger.TEN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.solovyev.common.NumberFormatter.DEFAULT_MAGNITUDE;
import static org.solovyev.common.NumberFormatter.NO_ROUNDING;

public class NumberFormatterTest {

    private NumberFormatter numberFormatter;

    @Before
    public void setUp() throws Exception {
        numberFormatter = new NumberFormatter();
    }

    @Test
    public void testEngineeringFormat() throws Exception {
        numberFormatter.useEngineeringFormat(5);
        assertEquals("0.1", numberFormatter.format(0.1d));
        assertEquals("0.01", numberFormatter.format(0.01d));
        assertEquals("0.001", numberFormatter.format(0.001d));
        assertEquals("5", numberFormatter.format(5d));
        assertEquals("5000", numberFormatter.format(5000d));
    }

    @Test
    public void testScientificFormatNoRounding() throws Exception {
        numberFormatter.useScientificFormat(DEFAULT_MAGNITUDE);
        numberFormatter.setPrecision(NO_ROUNDING);

        assertEquals("1", numberFormatter.format(1d));
        assertEquals("0.333333333333333", numberFormatter.format(1d / 3));
        assertEquals("3.333333333333333E-19", numberFormatter.format(pow(10, -18) / 3));
        assertEquals("1.23456789E18", numberFormatter.format(123456789 * pow(10, 10)));
        assertEquals("1E-16", numberFormatter.format(pow(10, -16)));
        assertEquals("5.999999999999995E18", numberFormatter.format(5999999999999994999d));

        testScientificFormat();
    }

    @Test
    public void testScientificFormatWithRounding() throws Exception {
        numberFormatter.useScientificFormat(DEFAULT_MAGNITUDE);
        numberFormatter.setPrecision(5);

        assertEquals("1", numberFormatter.format(1d));
        assertEquals("0.33333", numberFormatter.format(1d / 3));
        assertEquals("3.33333E-19", numberFormatter.format(pow(10, -18) / 3));
        assertEquals("1.23457E18", numberFormatter.format(123456789 * pow(10, 10)));
        assertEquals("1E-16", numberFormatter.format(pow(10, -16)));
        assertEquals("6E18", numberFormatter.format(5999999999999994999d));


        testScientificFormat();
    }

    @Test
    public void testSimpleFormatNoRounding() throws Exception {
        numberFormatter.useSimpleFormat();
        numberFormatter.setPrecision(NO_ROUNDING);

        assertEquals("1", numberFormatter.format(1d));
        assertEquals("0.000001", numberFormatter.format(pow(10, -6)));
        assertEquals("0.333333333333333", numberFormatter.format(1d / 3));
        assertEquals("3.333333333333333E-19", numberFormatter.format(pow(10, -18) / 3));
        assertEquals("1234567890000000000", numberFormatter.format(123456789 * pow(10, 10)));
        assertEquals("1E-16", numberFormatter.format(pow(10, -16)));
        assertEquals("1E-17", numberFormatter.format(pow(10, -17)));
        assertEquals("1E-18", numberFormatter.format(pow(10, -18)));
        assertEquals("1.5E-18", numberFormatter.format(1.5 * pow(10, -18)));
        assertEquals("1E-100", numberFormatter.format(pow(10, -100)));

        testSimpleFormat();
    }

    @Test
    public void testSimpleFormatWithRounding() throws Exception {
        numberFormatter.useSimpleFormat();
        numberFormatter.setPrecision(5);

        assertEquals("1", numberFormatter.format(1d));
        assertEquals("0", numberFormatter.format(pow(10, -6)));
        assertEquals("0.33333", numberFormatter.format(1d / 3));
        assertEquals("0", numberFormatter.format(pow(10, -18) / 3));
        assertEquals("1234567890000000000", numberFormatter.format(123456789 * pow(10, 10)));
        assertEquals("0", numberFormatter.format(pow(10, -16)));
        assertEquals("0", numberFormatter.format(pow(10, -17)));
        assertEquals("0", numberFormatter.format(pow(10, -18)));
        assertEquals("0", numberFormatter.format(1.5 * pow(10, -18)));
        assertEquals("0", numberFormatter.format(pow(10, -100)));

        testSimpleFormat();
    }

    // testing simple format with and without rounding
    private void testSimpleFormat() {
        assertEquals("0.00001", numberFormatter.format(pow(10, -5)));
        assertEquals("0.01", numberFormatter.format(3.11 - 3.1));

        assertEquals("100", numberFormatter.format(pow(10, 2)));
        assertEquals("1", numberFormatter.format(BigInteger.ONE));
        assertEquals("1000", numberFormatter.format(BigInteger.valueOf(1000)));

        assertEquals("1000000000000000000", numberFormatter.format(pow(10, 18)));
        assertEquals("1000000000000000000", numberFormatter.format(BigInteger.valueOf(10).pow(18)));

        assertEquals("1E19", numberFormatter.format(pow(10, 19)));
        assertEquals("1E19", numberFormatter.format(BigInteger.valueOf(10).pow(19)));

        assertEquals("1E20", numberFormatter.format(pow(10, 20)));
        assertEquals("1E20", numberFormatter.format(BigInteger.valueOf(10).pow(20)));

        assertEquals("1E100", numberFormatter.format(pow(10, 100)));
        assertEquals("1E100", numberFormatter.format(BigInteger.valueOf(10).pow(100)));

        assertEquals("0.01", numberFormatter.format(pow(10, -2)));

        assertEquals("5000000000000000000", numberFormatter.format(5000000000000000000d));
        assertEquals("5000000000000000000", numberFormatter.format(BigInteger.valueOf(5000000000000000000L)));

        assertEquals("5000000000000000000", numberFormatter.format(5000000000000000001d));
        assertEquals("5000000000000000001", numberFormatter.format(BigInteger.valueOf(5000000000000000001L)));

        assertEquals("5999999999999994900", numberFormatter.format(5999999999999994999d));
        assertEquals("5999999999999994999", numberFormatter.format(BigInteger.valueOf(5999999999999994999L)));

        assertEquals("5E19", numberFormatter.format(50000000000000000000d));
        assertEquals("5E19", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(19))));

        assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d));
        assertEquals("5E40", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(40))));
    }

    // testing scientific format with and without rounding
    private void testScientificFormat() {
        assertEquals("0.00001", numberFormatter.format(pow(10, -5)));
        assertEquals("1E-6", numberFormatter.format(pow(10, -6)));

        assertEquals("100", numberFormatter.format(pow(10, 2)));
        assertEquals("100", numberFormatter.format(TEN.pow(2)));

        assertEquals("10000", numberFormatter.format(pow(10, 4)));
        assertEquals("10000", numberFormatter.format(TEN.pow(4)));

        assertEquals("1E5", numberFormatter.format(pow(10, 5)));
        assertEquals("1E5", numberFormatter.format(TEN.pow(5)));

        assertEquals("1E18", numberFormatter.format(pow(10, 18)));
        assertEquals("1E18", numberFormatter.format(TEN.pow(18)));

        assertEquals("1E19", numberFormatter.format(pow(10, 19)));
        assertEquals("1E19", numberFormatter.format(TEN.pow( 19)));

        assertEquals("1E20", numberFormatter.format(pow(10, 20)));
        assertEquals("1E20", numberFormatter.format(TEN.pow(20)));

        assertEquals("1E100", numberFormatter.format(pow(10, 100)));
        assertEquals("1E100", numberFormatter.format(TEN.pow(100)));

        assertEquals("0.01", numberFormatter.format(pow(10, -2)));
        assertEquals("1E-17", numberFormatter.format(pow(10, -17)));
        assertEquals("1E-18", numberFormatter.format(pow(10, -18)));
        assertEquals("1.5E-18", numberFormatter.format(1.5 * pow(10, -18)));
        assertEquals("1E-100", numberFormatter.format(pow(10, -100)));

        assertEquals("5E18", numberFormatter.format(5000000000000000000d));
        assertEquals("5E18", numberFormatter.format(5000000000000000001d));
        assertEquals("5E19", numberFormatter.format(50000000000000000000d));
        assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d));
    }

    @Test
    public void testMaximumPrecision() throws Exception {
        numberFormatter.useSimpleFormat();
        numberFormatter.setPrecision(10);

        for (int i = 0; i < 1000; i++) {
            for (int j = 2; j < 1000; j += j - 1) {
                for (int k = 2; k < 1000; k += k - 1) {
                    final double first = makeDouble(i, j);
                    final double second = makeDouble(i, 1000 - k);
                    checkMaximumPrecision(first + "-" + second, numberFormatter.format(first - second));
                    checkMaximumPrecision(second + "-" + first, numberFormatter.format(second - first));
                    checkMaximumPrecision(second + "+" + first, numberFormatter.format(first + second));
                }
            }
        }
    }

    private void checkMaximumPrecision(String expression, CharSequence value) {
        assertTrue(expression + "=" + value, value.length() <= 8);
    }

    private static double makeDouble(int integerPart, int fractionalPart) {
        return Double.parseDouble(integerPart + "." + fractionalPart);
    }
}