/*
 * Copyright 2013 serso aka se.solovyev
 *
 * 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.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Contact details
 *
 * Email: se.solovyev@gmail.com
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.calculator.model;

import jscl.AngleUnit;
import jscl.JsclMathEngine;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.Expression;
import jscl.math.function.CustomFunction;
import jscl.text.ParseException;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import org.solovyev.android.calculator.BaseCalculatorTest;
import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.variables.CppVariable;

import static org.junit.Assert.fail;

public class AndroidEngineTest extends BaseCalculatorTest {

    @Before
    public void setUp() throws Exception {
        super.setUp();
        engine.getMathEngine().setPrecision(3);
    }

    @Test
    public void testDegrees() throws Exception {
        final MathEngine me = engine.getMathEngine();
        final AngleUnit defaultAngleUnit = me.getAngleUnits();
        try {
            me.setAngleUnits(AngleUnit.rad);
            me.setPrecision(3);
            assertError("°");
            assertEval("0.017", "1°");
            assertEval("0.349", "20.0°");
            assertEval("0.5", "sin(30°)");
            assertEval("0.524", "asin(sin(30°))");
            assertEval("∂(cos(t), t, t, 1°)", "∂(cos(t),t,t,1°)");

            assertEval("∂(cos(t), t, t, 1°)", "∂(cos(t),t,t,1°)", JsclOperation.simplify);
        } finally {
            me.setAngleUnits(defaultAngleUnit);
        }
    }

    @Test
    public void testFormatting() throws Exception {
        final MathEngine me = engine.getMathEngine();
        assertEval("12 345", me.simplify("12345"));
    }

    @Test
    public void testI() throws ParseException {
        final MathEngine me = engine.getMathEngine();

        assertEval("-i", me.evaluate("i^3"));
        for (int i = 0; i < 1000; i++) {
            double real = (Math.random() - 0.5) * 1000;
            double imag = (Math.random() - 0.5) * 1000;
            int exp = (int) (Math.random() * 10);

            final StringBuilder sb = new StringBuilder();
            sb.append(real);
            if (imag > 0) {
                sb.append("+");
            }
            sb.append(imag);
            sb.append("^").append(exp);
            try {
                me.evaluate(sb.toString());
            } catch (Throwable e) {
                fail(sb.toString());
            }
        }
    }

    @Test
    public void testEmptyFunction() throws Exception {
        final MathEngine me = engine.getMathEngine();
        try {
            me.evaluate("cos(cos(cos(cos(acos(acos(acos(acos(acos(acos(acos(acos(cos(cos(cos(cos(cosh(acos(cos(cos(cos(cos(cos(acos(acos(acos(acos(acos(acos(acos(acos(cos(cos(cos(cos(cosh(acos(cos())))))))))))))))))))))))))))))))))))))");
            Assert.fail();
        } catch (ParseException ignored) {
        }
        assertEval("0.34+1.382i", "ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(ln(100)))))))))))))))");
        try {
            me.evaluate("cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos())))))))))))))))))))))))))))))))))))");
            Assert.fail();
        } catch (ParseException ignored) {
        }

        final AngleUnit defaultAngleUnit = me.getAngleUnits();
        try {
            me.setAngleUnits(AngleUnit.rad);
            assertEval("0.739", me.evaluate("cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(1))))))))))))))))))))))))))))))))))))"));
        } finally {
            me.setAngleUnits(defaultAngleUnit);
        }

        engine.getVariablesRegistry().addOrUpdate(CppVariable.builder("si").withValue(5d).build().toJsclConstant());
        assertEval("5", me.evaluate("si"));

        assertError("sin");
    }

    @Test
    public void testRounding() throws Exception {
        final MathEngine me = engine.getMathEngine();

        try {
            me.setGroupingSeparator('\'');
            me.setPrecision(2);
            assertEval("12'345'678.9", me.evaluate("1.23456789E7"));
            me.setPrecision(10);
            assertEval("12'345'678.9", me.evaluate("1.23456789E7"));
            assertEval("123'456'789", me.evaluate("1.234567890E8"));
            assertEval("1'234'567'890.1", me.evaluate("1.2345678901E9"));
        } finally {
            me.setPrecision(3);
            me.setGroupingSeparator(JsclMathEngine.GROUPING_SEPARATOR_DEFAULT);
        }
    }

    @Test
    public void testNumeralSystems() throws Exception {
        final MathEngine me = engine.getMathEngine();

        assertEval("11 259 375", "0x:ABCDEF");
        assertEval("30 606 154.462", "0x:ABCDEF*e");
        assertEval("30 606 154.462", "e*0x:ABCDEF");
        assertEval("e", "e*0x:ABCDEF/0x:ABCDEF");
        assertEval("30 606 154.462", "0x:ABCDEF*e*0x:ABCDEF/0x:ABCDEF");
        assertEval("30 606 154.462", "c+0x:ABCDEF*e*0x:ABCDEF/0x:ABCDEF-c+0x:C-0x:C");
        assertEval("1 446 257 064 651.832", "28*28 * sin(28) - 0b:1101 + √(28) + exp ( 28) ");
        assertEval("13", "0b:1101");

        assertError("0b:π");

        final NumeralBase defaultNumeralBase = me.getNumeralBase();
        try {
            me.setNumeralBase(NumeralBase.bin);
            assertEval("101", "10+11");
            assertEval("0.101", "10/11");

            me.setNumeralBase(NumeralBase.hex);
            assertEval("63 7B", "56CE+CAD");
            assertEval("E", "E");
        } finally {
            me.setNumeralBase(defaultNumeralBase);
        }
    }

    @Test
    public void testLog() throws Exception {
        final MathEngine me = engine.getMathEngine();

        assertEval("∞", Expression.valueOf("1/0").numeric().toString());
        assertEval("∞", Expression.valueOf("ln(10)/ln(1)").numeric().toString());

        // logarithm
        assertEval("ln(x)/ln(base)", ((CustomFunction) me.getFunctionsRegistry().get("log")).getContent());
        assertEval("∞", "log(1, 10)");
        assertEval("3.322", "log(2, 10)");
        assertEval("1.431", "log(5, 10)");
        assertEval("0.96", "log(11, 10)");
        assertEval("1/(bln(a))", "∂(log(a, b), b)", JsclOperation.simplify);
        assertEval("-ln(b)/(aln(a)^2)", "∂(log(a, b), a)", JsclOperation.simplify);

    }
}
