package org.apache.velocity.test;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.velocity.runtime.parser.node.MathUtils;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * Test arithmetic operations. Introduced after extending from Integer-only
 * to Number-handling.
 *
 * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
 */
public class ArithmeticTestCase extends TestCase
{

    public ArithmeticTestCase(String testName)
    {
        super(testName);
    }

    public static Test suite()
    {
       return new TestSuite(ArithmeticTestCase.class);
    }

    public void testAdd()
    {
        addHelper (10, (short) 20, 30, Integer.class);
        addHelper ((byte) 10, (short) 20, 30, Short.class);
        addHelper (10f, (short) 20, 30, Float.class);
        addHelper ((byte) 10, 20d, 30, Double.class);
        addHelper (BigInteger.valueOf(10), 20, 30, BigInteger.class);
        addHelper (20, BigDecimal.valueOf(10),  30, BigDecimal.class);

        // Test overflow
        addHelper (Integer.MAX_VALUE, (short) 20, (double)Integer.MAX_VALUE+20, Long.class);
        addHelper (20, Long.MAX_VALUE, (double)Long.MAX_VALUE+20, BigInteger.class);
        addHelper (-20, Long.MIN_VALUE, (double)Long.MIN_VALUE-20, BigInteger.class);
    }

    private void addHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.add( n1, n2);
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals("ResultType does not match.", expectedResultType, result.getClass());
    }

    public void testSubtract()
    {
        subtractHelper (100, (short) 20, 80, Integer.class);
        subtractHelper ((byte) 100, (short) 20, 80, Short.class);
        subtractHelper (100f, (short) 20, 80, Float.class);
        subtractHelper ((byte) 100, 20d, 80, Double.class);
        subtractHelper (BigInteger.valueOf(100), 20, 80, BigInteger.class);
        subtractHelper (100, BigDecimal.valueOf(20),  80, BigDecimal.class);

        // Test overflow
        subtractHelper (Integer.MIN_VALUE, (short) 20, (double)Integer.MIN_VALUE-20, Long.class);
        subtractHelper(-20, Long.MAX_VALUE, -20d - (double) Long.MAX_VALUE, BigInteger.class);
        subtractHelper(Integer.MAX_VALUE, Long.MIN_VALUE, (double) Long.MAX_VALUE + (double) Integer.MAX_VALUE, BigInteger.class);
    }

    private void subtractHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.subtract( n1, n2);
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals("ResultType does not match.", expectedResultType, result.getClass());
    }

    public void testMultiply()
    {
        multiplyHelper (10, (short) 20, 200, Integer.class);
        multiplyHelper ((byte) 100, (short) 20, 2000, Short.class);
        multiplyHelper ((byte) 100, (short) 2000, 200000, Integer.class);
        multiplyHelper (100f, (short) 20, 2000, Float.class);
        multiplyHelper ((byte) 100, 20d, 2000, Double.class);
        multiplyHelper (BigInteger.valueOf(100), 20, 2000, BigInteger.class);
        multiplyHelper (100, BigDecimal.valueOf(20),  2000, BigDecimal.class);

        // Test overflow
        multiplyHelper (Integer.MAX_VALUE, (short) 10, (double)Integer.MAX_VALUE*10d, Long.class);
        multiplyHelper(Integer.MAX_VALUE, (short) -10, (double) Integer.MAX_VALUE * -10d, Long.class);
        multiplyHelper(20, Long.MAX_VALUE, 20d * (double) Long.MAX_VALUE, BigInteger.class);
    }

    private void multiplyHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.multiply( n1, n2);
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals("ResultType does not match.", expectedResultType, result.getClass());
    }

    public void testDivide()
    {
        divideHelper (10, (short) 2, 5, Integer.class);
        divideHelper ((byte) 10, (short) 2, 5, Short.class);
        divideHelper (BigInteger.valueOf(10), (short) 2, 5, BigInteger.class);
        divideHelper (10, (short) 4, 2, Integer.class);
        divideHelper (10, 2.5f, 4, Float.class);
        divideHelper(10, 2.5, 4, Double.class);
        divideHelper(10, new BigDecimal(2.5), 4, BigDecimal.class);
    }

    private void divideHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.divide( n1, n2);
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals("ResultType does not match.", expectedResultType, result.getClass());
    }

    public void testModulo()
    {
        moduloHelper (10, (short) 2, 0, Integer.class);
        moduloHelper ((byte) 10, (short) 3, 1, Short.class);
        moduloHelper(BigInteger.valueOf(10), (short) 4, 2, BigInteger.class);
        moduloHelper(10, 5.5f, 4.5, Float.class);

        try
        {
            moduloHelper (10, new BigDecimal( 2.5), 4, BigDecimal.class);
            fail ("Modulo with BigDecimal is not allowed! Should have thrown an ArithmeticException.");
        }
        catch( ArithmeticException e)
        {
            // do nothing
        }
    }

    private void moduloHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.modulo( n1, n2);
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals("ResultType does not match.", expectedResultType, result.getClass());
    }

    public void testCompare()
    {
        compareHelper (10, (short) 10, 0);
        compareHelper (10, (short) 11, -1);
        compareHelper (BigInteger.valueOf(10), (short) 11, -1);
        compareHelper ((byte) 10, (short) 3, 1);
        compareHelper(10f, (short) 11, -1);
        compareHelper(10d, (short) 11, -1);
    }

    private void compareHelper (Number n1, Number n2, int expectedResult)
    {
        int result = MathUtils.compare( n1, n2 );
        assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result);
    }

    public void testNegate()
    {
        negateHelper((byte) 1, -1, Byte.class);
        negateHelper((short) 1, -1, Short.class);
        negateHelper(1, -1, Integer.class);
        negateHelper(1L, -1, Long.class);
        negateHelper(BigInteger.valueOf(1), -1, BigInteger.class);
        negateHelper(BigDecimal.valueOf(1), -1, BigDecimal.class);
        negateHelper(Long.MIN_VALUE, BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.valueOf(1)).doubleValue(), BigInteger.class);
    }

    private void negateHelper(Number n, double expectedResult, Class expectedResultType)
    {
        Number result = MathUtils.negate(n);
        assertEquals ("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
        assertEquals ("ResultType does not match.", expectedResultType, result.getClass());
    }

/*
 *
 *    COMMENT OUT FOR PERFORMANCE-MEASSUREMENTS
 *
 *    public void testProfile()
 *    {
 *
 *        long start = System.currentTimeMillis();
 *
 *        Number v1 = Long.valueOf(1000);
 *        Number v2 = Double.valueOf(10.23);
 *        Number result = null;
 *        for (int a = 0; a < 10000; a++)
 *        {
 *
 *            result = MathUtils.typeConvert (
 *                new BigDecimal (v1.doubleValue()).add (
 *                new BigDecimal (v2.doubleValue())), v1, v2, false);
 *
 *        }
 *
 *        System.out.println ("took: "+(System.currentTimeMillis()-start));
 *
 *        start = System.currentTimeMillis();
 *        for (int a = 0; a < 10000; a++)
 *        {
 *
 *            result = MathUtils.divide( v1, v2);
 *        }
 *
 *        Number result2 = result;
 *        System.out.println ("took: "+(System.currentTimeMillis()-start));
 *    }
 *
 */

    /**
     * Test additional functions
     */
    public void testIsZero()
    {
        assertTrue (MathUtils.isZero (0));
        assertTrue (!MathUtils.isZero (1));
        assertTrue (!MathUtils.isZero (-1));

        assertTrue (MathUtils.isZero (0f));
        assertTrue (!MathUtils.isZero (0.00001f));
        assertTrue (!MathUtils.isZero (-0.00001f));

    }

}
