package jscl.math;

import org.junit.Test;
import org.solovyev.common.NumberFormatter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import jscl.AngleUnit;
import jscl.JsclMathEngine;
import jscl.MathEngine;
import jscl.NumeralBase;
import jscl.math.function.Constant;
import jscl.math.function.ExtendedConstant;
import jscl.math.function.IConstant;
import jscl.text.ParseException;
import midpcalc.Real;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class ExpressionTest {

    private static final String expressions = "-24.37581129610191-((2699.798527427213-4032.781981216783)*√(4657.120529143301)/6202.47137988087-ln(4435.662292261872)*sin(5134.044125137488)-sin(5150.617980207194)+sin(1416.6029070906816))\n" +
            "1.6796699432963022E11-((5709.375015847543-√(9582.238699996864))*3622.6983393324695*8262.5649407951-4677.148654973858*ln(7443.120012194502)*ln(8771.583007058995)-7796.8909039525515)\n" +
            "73260.62134636212-(((8211.239143650871+9653.120869092472/7201.080677271473-3675.134705789929/sin(7383.23886608315))+sin(8201.936690357508)/9797.420229466312/4487.554672699577))\n" +
            "7835770.323315129-(3053.1562785415554*2564.6140313677965+ln(3376.462881190876)/2722.807595157415+cos(1654.053577173823)/4481.384989253306+cos(8539.28578313432)+5603.074520175994)\n" +
            "7.219848990044144E11-((((9742.199604684844*8637.637793906879)-8613.230244786755+√(1026.016931180783))*8580.654028379886+ln(391.54269092744664)/√(4341.52889100337))+sin(508.2338437131828))\n" +
            "6685.424634765305-((3746.8598111083793*sin(4784.284503822155)-ln(2218.167104685851)-sin(4794.102351616163))+3063.0457850324233-9545.89841181986/7482.886158430515/√(4001.7788453452417))\n" +
            "4108.351107289166-((4102.493099763215-cos(5125.896955144614))+cos(3540.5378825149537)/5495.082697662915-8681.097948569084/cos(8032.923414105565)/4501.859274666647-cos(2711.854814853617))\n" +
            "-1.0620650024203222-(((750.111466515082-9102.643276012855+3065.780766976849+2861.8661641038534)*3536.5716528042535/1106.4220238862831/7308.354645022433/sin(1173.0557272435349)))\n" +
            "76379.44709543366-(((9932.156860771898+ln(7185.939808298219)*7687.141207402175)+cos(8185.971595673607)+ln(3977.781005305916)+cos(2376.681088176604)*2201.8644681719-√(3135.5682620873513)))\n" +
            "635.3559760598341-((8075.628923197531/8255.66812901165+√(2936.433021287237)*sin(7502.632251185349)*sin(3225.272171990918)+613.2028126347367+ln(8485.99141046724))-cos(8518.190742544848))\n" +
            "-7.51034737891574E7-(4640.543920377892/√(4363.843503017953)*√(7152.285785189239)*sin(7908.617128515873)*√(6906.317696310425)*6562.864387786373/ln(4988.784292770342)-sin(5488.826440303076))\n" +
            "-5932.595870545627-(((3010.4402565484047-3218.3878293708044)+sin(9074.010686307622))/cos(7656.587621759453)/cos(1187.7115449548426)+cos(2207.5981975517957)/sin(7170.633198376899)+cos(129.16231777575283))\n" +
            "14603.51285508874-((1505.6670065700584-ln(7760.688872668162)-cos(1521.0119520475184)+5874.745001223881+sin(5672.757849045151)*sin(9740.028947007728)+7239.645067283123)-ln(1198.788813287901))\n" +
            "13789.681143529104-(4837.182498312745-sin(8683.238702053257)+9725.382455542274-ln(6866.318581911774)*√(7639.899860231787)-cos(8486.508690243441)/√(3325.7578426126165)/sin(5655.089763857597))\n" +
            "5.9142041337333955E7-((((6945.350108837433-6875.255304556105-5503.241468583639*ln(4882.916231493727)-8221.764146581652)*4816.727562192865)-47.13141200212378)*sin(7032.925165237175))\n" +
            "5.307098467139001E7-((((1472.5507104204128-2244.0144093640956)/337.94074333738934-6119.909773145814/4030.814210676087)+7955.59068044787)*6674.093078737379+cos(1072.8762639281485))\n" +
            "2.4791276864495695E8-(((4650.104984872984*5990.69176729321*ln(7326.221240600894)-√(4166.293207980269)-cos(2930.9607978551735))+4892.051672831694)-√(4643.4262014756005)*ln(4322.391733256239))\n" +
            "1.000473116354486E7-((1856.1678375267843*3375.5973472957558+8102.216834762455*460.5133278219642)+ln(1077.2976545272872)+9836.94091820254/cos(561.8742170542756)*sin(9587.941076809435))\n" +
            "1.043950271691602E7-((((3594.0668967195334*2903.435684617801-ln(431.72508853349336)+√(2631.9717706394795)+4315.178672680215)+sin(1034.406679999502)/cos(7200.345388541185))+sin(8030.470700471927)))\n" +
            "-4612.867091103858-(((117.31001770566519/6314.371065466436/5793.914918630644*1016.2707467350263*8539.984705173652)/3647.0016733225143*8871.091071924995-4680.559579608435))";

    public static void main(String[] args) {
        System.out.println("Result: " + getWolframAlphaResult("APP_ID", "-24.37581129610191-((2699.798527427213-4032.781981216783)*√(4657.120529143301)/6202.47137988087-ln(4435.662292261872)*sin(5134.044125137488)-sin(5150.617980207194)+sin(1416.6029070906816))"));
        /*final StringTokenizer st = new StringTokenizer(expressions, "\n");
          if ( st.hasMoreTokens() ) {
              final String expression = st.nextToken();
              final String result = getWolframAlphaResult("APP_ID", expression);
              System.out.println("Expression: " + expression);
              System.out.println("Result: " + result);
              try {
                  final Double value = Double.valueOf(result);
              } catch (NumberFormatException e) {
                  e.printStackTrace();
              }
          }*/
    }

    @Nullable
    public static String getWolframAlphaResult(@Nonnull String appId, @Nonnull String expression) {
        String result = null;

        URL wolframAlphaUrl;
        try {
            wolframAlphaUrl = new URL("http://api.wolframalpha.com/v2/query?input=" + expression + "&appid=" + appId + "&format=plaintext&podtitle=Decimal+approximation");

            final URLConnection connection = wolframAlphaUrl.openConnection();
            BufferedReader in = null;
            try {
                in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

                String line;
                while ((line = in.readLine()) != null) {
                    System.out.println(line);
                    if (line.contains("<plaintext>")) {
                        result = line.replace("<plaintext>", "").replace("</plaintext>", "").replace("...", "").trim();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    in.close();
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    @Test
    public void testImag() throws Exception {
        assertEquals("-i", Expression.valueOf("i^3").numeric().toString());
    }

    @Test
    public void testConstants() throws Exception {
        assertTrue(Expression.valueOf("3+4").getConstants().isEmpty());

        Set<? extends Constant> constants = Expression.valueOf("3+4*t").getConstants();
        assertTrue(constants.size() == 1);
        assertTrue(constants.contains(new Constant("t")));

        IConstant constant = null;

        final JsclMathEngine me = JsclMathEngine.getInstance();
        try {
            final ExtendedConstant.Builder t_0 = new ExtendedConstant.Builder(new Constant("t_0"), 1d);
            constant = me.getConstantsRegistry().addOrUpdate(t_0.create());

            constants = Expression.valueOf("3+4*t_0+t_0+t_1").getConstants();
            assertTrue(constants.size() == 2);
            assertTrue(constants.contains(new Constant("t_0")));
            assertTrue(constants.contains(new Constant("t_1")));

            final Expression expression = Expression.valueOf("2*t_0+5*t_1");

            assertEquals("7", expression.substitute(new Constant("t_1"), Expression.valueOf(1.0)).numeric().toString());
            assertEquals("12", expression.substitute(new Constant("t_1"), Expression.valueOf(2.0)).numeric().toString());
            assertEquals("27", expression.substitute(new Constant("t_1"), Expression.valueOf(5.0)).numeric().toString());

        } finally {
            if (constant != null) {
                final ExtendedConstant.Builder jBuilder = new ExtendedConstant.Builder(new Constant(constant.getName()), (String) null);
                me.getConstantsRegistry().addOrUpdate(jBuilder.create());
            }
        }
    }

    @Test
    public void testExpressions() throws Exception {
        assertEquals("3", Expression.valueOf("3").numeric().toString());
        assertEquals("0.693147180559945", Expression.valueOf("ln(2)").numeric().toString());
        assertEquals("1", Expression.valueOf("lg(10)").numeric().toString());
        assertEquals("0", Expression.valueOf("eq(0, 1)").numeric().toString());
        assertEquals("1", Expression.valueOf("eq(1, 1)").numeric().toString());

        assertEquals("24", Expression.valueOf("4!").numeric().toString());
        try {
            Expression.valueOf("(-3+2)!").numeric().toString();
            fail();
        } catch (ArithmeticException e) {

        }
        assertEquals("24", Expression.valueOf("(2+2)!").numeric().toString());
        assertEquals("120", Expression.valueOf("(2+2+1)!").numeric().toString());
        assertEquals("24", Expression.valueOf("(2.0+2.0)!").numeric().toString());
        assertEquals("24", Expression.valueOf("4.0!").numeric().toString());
        assertEquals("48", Expression.valueOf("2*4.0!").numeric().toString());
        assertEquals("40320", Expression.valueOf("(2*4.0)!").numeric().toString());

        final JsclMathEngine me = JsclMathEngine.getInstance();
        final AngleUnit angleUnits = me.getAngleUnits();
        try {
            me.setAngleUnits(AngleUnit.rad);
            assertEquals("-0.905578362006624", Expression.valueOf("sin(4!)").numeric().toString());
        } finally {
            me.setAngleUnits(angleUnits);
        }
        assertEquals("1", Expression.valueOf("(3.14/3.14)!").numeric().toString());
        assertEquals("1", Expression.valueOf("2/2!").numeric().toString());
        try {
            assertEquals("3.141592653589793!", Expression.valueOf("3.141592653589793!").numeric().toString());
            fail();
        } catch (NotIntegerException e) {

        }
        assertEquals("0.523598775598299", Expression.valueOf("3.141592653589793/3!").numeric().toString());
        try {
            assertEquals("3.141592653589793/3.141592653589793!", Expression.valueOf("3.141592653589793/3.141592653589793!").numeric().toString());
            fail();
        } catch (ArithmeticException e) {

        }
        try {
            assertEquals("7.2!", Expression.valueOf("7.2!").numeric().toString());
            fail();
        } catch (NotIntegerException e) {
        }

        try {
            assertEquals("ln(7.2!)", Expression.valueOf("ln(7.2!)").numeric().toString());
            fail();
        } catch (NotIntegerException e) {
        }

        assertEquals("ln(7.2!)", Expression.valueOf("ln(7.2!)").simplify().toString());


        assertEquals("36", Expression.valueOf("3!^2").numeric().toString());
        assertEquals("1", Expression.valueOf("(π/π)!").numeric().toString());
        assertEquals("720", Expression.valueOf("(3!)!").numeric().toString());
        assertEquals("36", Expression.valueOf("3!*3!").numeric().toString());

        assertEquals("100", Expression.valueOf("0.1E3").numeric().toString());

        final AngleUnit defaultAngleUnits = me.getAngleUnits();
        try {
            me.setAngleUnits(AngleUnit.rad);
            assertEquals("0.017453292519943", Expression.valueOf("1°").numeric().toString());
            assertEquals("0.034906585039887", Expression.valueOf("2°").numeric().toString());
            assertEquals("0.05235987755983", Expression.valueOf("3°").numeric().toString());
            assertEquals("0.261799387799149", Expression.valueOf("3°*5").numeric().toString());
            assertEquals("0.00274155677808", Expression.valueOf("3°^2").numeric().toString());
            assertEquals("0.010966227112322", Expression.valueOf("3!°^2").numeric().toString());
            assertEquals("0.00091385225936", Expression.valueOf("3°°").numeric().toString());
            assertEquals("0.087266462599716", Expression.valueOf("5°").numeric().toString());
            assertEquals("2.05235987755983", Expression.valueOf("2+3°").numeric().toString());
        } finally {
            me.setAngleUnits(defaultAngleUnits);
        }

        try {
            me.setAngleUnits(AngleUnit.deg);
            assertEquals("1", Expression.valueOf("1°").numeric().toString());
            assertEquals("2", Expression.valueOf("2°").numeric().toString());
            assertEquals("3", Expression.valueOf("3°").numeric().toString());
            assertEquals("15", Expression.valueOf("3°*5").numeric().toString());
            assertEquals("9", Expression.valueOf("3°^2").numeric().toString());
            assertEquals("36", Expression.valueOf("3!°^2").numeric().toString());
            assertEquals("3", Expression.valueOf("3°°").numeric().toString());
            assertEquals("5", Expression.valueOf("5°").numeric().toString());
            assertEquals("5", Expression.valueOf("2+3°").numeric().toString());
        } finally {
            me.setAngleUnits(defaultAngleUnits);
        }

        assertEquals("6", Expression.valueOf("2*∂(3*x,x)").expand().toString());
        assertEquals("3", Expression.valueOf("∂(3*x,x)").expand().toString());
        assertEquals("12", Expression.valueOf("∂(x^3,x,2)").expand().toString());
        assertEquals("3*a", Expression.valueOf("∂(3*x*a,x)").expand().toString());
        assertEquals("0", Expression.valueOf("∂(3*x*a,x,0.011,2)").expand().toString());
        assertEquals("0", Expression.valueOf("2*∂(3*x*a,x,0.011,2)").expand().toString());
        assertEquals("ln(8)+lg(8)*ln(8)", Expression.valueOf("ln(8)*lg(8)+ln(8)").expand().toString());
        assertEquals("3.957364376505986", Expression.valueOf("ln(8)*lg(8)+ln(8)").numeric().toString());

        assertEquals("4!", Expression.valueOf("4.0!").simplify().toString());
        assertEquals("4°", Expression.valueOf("4.0°").simplify().toString());
        assertEquals("30°", Expression.valueOf("30°").simplify().toString());


        assertEquals("1", Expression.valueOf("abs(1)").numeric().toString());
        assertEquals("0", Expression.valueOf("abs(0)").numeric().toString());
        assertEquals("0", Expression.valueOf("abs(-0)").numeric().toString());
        assertEquals("1", Expression.valueOf("abs(-1)").numeric().toString());
        assertEquals("∞", Expression.valueOf("abs(-∞)").numeric().toString());

        assertEquals("1", Expression.valueOf("abs(i)").numeric().toString());
        assertEquals("0", Expression.valueOf("abs(0+0*i)").numeric().toString());
        assertEquals("1", Expression.valueOf("abs(-i)").numeric().toString());
        assertEquals("2.23606797749979", Expression.valueOf("abs(2-i)").numeric().toString());
        assertEquals("2.23606797749979", Expression.valueOf("abs(2+i)").numeric().toString());
        assertEquals("2.82842712474619", Expression.valueOf("abs(2+2*i)").numeric().toString());
        assertEquals("2.82842712474619", Expression.valueOf("abs(2-2*i)").numeric().toString());

        try {
            final ExtendedConstant.Builder k = new ExtendedConstant.Builder(new Constant("k"), 2.8284271247461903);
            me.getConstantsRegistry().addOrUpdate(k.create());
            assertEquals("k", Expression.valueOf("k").numeric().toString());
            assertEquals("k", Expression.valueOf("k").simplify().toString());
            assertEquals("k", Expression.valueOf("k").simplify().toString());
            assertEquals("k^3", Expression.valueOf("k*k*k").simplify().toString());
            assertEquals("22.62741699796953", Expression.valueOf("k*k*k").numeric().toString());
        } finally {
            final ExtendedConstant.Builder k = new ExtendedConstant.Builder(new Constant("k"), (String) null);
            me.getConstantsRegistry().addOrUpdate(k.create());
        }

        try {
            final ExtendedConstant.Builder k_1 = new ExtendedConstant.Builder(new Constant("k_1"), 3d);
            me.getConstantsRegistry().addOrUpdate(k_1.create());
            assertEquals("k_1", Expression.valueOf("k_1").numeric().toString());
            assertEquals("k_1", Expression.valueOf("k_1[0]").numeric().toString());
            assertEquals("k_1", Expression.valueOf("k_1[2]").numeric().toString());
        } finally {
            final ExtendedConstant.Builder k_1 = new ExtendedConstant.Builder(new Constant("k_1"), (String) null);
            me.getConstantsRegistry().addOrUpdate(k_1.create());
        }

        Generic expression = me.simplifyGeneric("cos(t)+∂(cos(t),t)");
        Generic substituted = expression.substitute(new Constant("t"), Expression.valueOf(100d));
        assertEquals("-1.158455930679138", substituted.numeric().toString());

        expression = me.simplifyGeneric("abs(t)^2+2!");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("102", substituted.numeric().toString());


        expression = me.simplifyGeneric("abs(t)^2+10%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("110", substituted.numeric().toString());

        expression = me.simplifyGeneric("abs(t)^2-10%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("90", substituted.numeric().toString());

        expression = me.simplifyGeneric("(abs(t)^2)*10%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("10", substituted.numeric().toString());

        expression = me.simplifyGeneric("(abs(t)^2)/10%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("1000", substituted.numeric().toString());

        expression = me.simplifyGeneric("abs(t)^2+t%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("110", substituted.numeric().toString());

        expression = me.simplifyGeneric("abs(t)^2-t%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("90", substituted.numeric().toString());

        expression = me.simplifyGeneric("(abs(t)^2)*t%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("10", substituted.numeric().toString());

        expression = me.simplifyGeneric("(abs(t)^2)/t%");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("1000", substituted.numeric().toString());

        expression = me.simplifyGeneric("Σ(t, t, 0, 10)");
        assertEquals("55", expression.numeric().toString());

        expression = me.simplifyGeneric("Σ(t, t, 0, 10)");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("55", substituted.numeric().toString());

        expression = me.simplifyGeneric("10*Σ(t, t, 0, 10)");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("550", substituted.numeric().toString());

        expression = me.simplifyGeneric("t*Σ(t, t, 0, 10)");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("550", substituted.numeric().toString());

        expression = me.simplifyGeneric("t*Σ(t+100%, t, 0, 10)");
        substituted = expression.substitute(new Constant("t"), Expression.valueOf(10d));
        assertEquals("1100", substituted.numeric().toString());

        assertEquals("i*t", Expression.valueOf("i*t").expand().simplify().toString());
        assertEquals("t", Expression.valueOf("t").simplify().toString());
        assertEquals("t^3", Expression.valueOf("t*t*t").simplify().toString());

        try {
            Expression.valueOf("t").numeric();
            fail();
        } catch (ArithmeticException e) {
        }

        final ExtendedConstant.Builder t = new ExtendedConstant.Builder(new Constant("t"), (String) null);
        me.getConstantsRegistry().addOrUpdate(t.create());
        try {
            Expression.valueOf("t").numeric();
            fail();
        } catch (ArithmeticException e) {
        }

        assertEquals("√(1+t)/(1+t)", Expression.valueOf("1/√(1+t)").simplify().toString());

        assertEquals("t", Expression.valueOf("t").simplify().toString());
        assertEquals("t^3", Expression.valueOf("t*t*t").simplify().toString());

        try {
            me.setAngleUnits(AngleUnit.rad);
            assertEquals("0.693147180559945+Π*i", Expression.valueOf("ln(-2)").numeric().toString());
        } finally {
            me.setAngleUnits(AngleUnit.deg);
        }
        assertEquals("-2/57", Expression.valueOf("1/(-57/2)").simplify().toString());
        assertEquals("sin(30)", Expression.valueOf("sin(30)").expand().toString());
        assertEquals("sin(n)", Expression.valueOf("sin(n)").expand().toString());
        assertEquals("sin(n!)", Expression.valueOf("sin(n!)").expand().toString());
        assertEquals("sin(n°)", Expression.valueOf("sin(n°)").expand().toString());
        assertEquals("sin(30°)", Expression.valueOf("sin(30°)").expand().toString());
        assertEquals("0.5", Expression.valueOf("sin(30°)").expand().numeric().toString());
        assertEquals("sin(2!)", Expression.valueOf("sin(2!)").expand().toString());

        assertEquals("12", Expression.valueOf("3*(3+1)").expand().toString());
        assertEquals("114.5915590261647", Expression.valueOf("deg(2)").numeric().toString());
        try {
            assertEquals("-0.1425465430742778", Expression.valueOf("∏(tan(3))").numeric().toString());
            fail();
        } catch (ParseException e) {
        }
        try {
            assertEquals("-0.14255", Expression.valueOf("sin(2,2)").expand().numeric().toString());
            fail();
        } catch (ParseException e) {
        }
        try {
            assertEquals("114.59155902616465", Expression.valueOf("deg(2,2)").numeric().toString());
            fail();
        } catch (ParseException e) {
        }

        assertEquals("0.5", Expression.valueOf("sin(30°)").numeric().toString());
        assertEquals("π", Expression.valueOf("√(π)^2").simplify().toString());
        assertEquals("π", Expression.valueOf("√(π^2)").simplify().toString());
        assertEquals("π^2", Expression.valueOf("√(π^2*π^2)").simplify().toString());
        assertEquals("π^3", Expression.valueOf("√(π^4*π^2)").simplify().toString());
        assertEquals("e*π^2", Expression.valueOf("√(π^4*e^2)").simplify().toString());

        assertEquals("1", Expression.valueOf("(π/π)!").numeric().toString());

        // in deg mode π=180 and factorial of 180 is calculating
        assertEquals("0", Expression.valueOf("Π/Π!").numeric().toString());

        assertEquals("0*i", Expression.valueOf("exp((Π*i))+1").numeric().toString());
        assertEquals("20*x^3", Expression.valueOf("∂(5*x^4, x)").expand().simplify().toString());
        assertEquals("25*x", Expression.valueOf("5*x*5").expand().simplify().toString());
        assertEquals("20*x", Expression.valueOf("5*x*4").expand().simplify().toString());

        try {
            me.evaluate("0b:π");
            fail();
        } catch (ParseException e) {
            // ok
        }

        try {
            me.evaluate("0b:10π");
            fail();
        } catch (ParseException e) {
            // ok
        }

        try {
            me.setNumeralBase(NumeralBase.hex);

            assertEquals("0.EEEEEEEEEEEEEC88", me.evaluate("0x:E/0x:F"));
            assertEquals("E/F", me.simplify("0x:E/0x:F"));

            assertEquals("0.EEEEEEEEEEEEEC88", me.evaluate("E/F"));
            assertEquals("E/F", me.simplify("E/F"));

        } finally {
            me.setNumeralBase(NumeralBase.dec);
        }

        try {
            me.setAngleUnits(AngleUnit.rad);
            assertEquals("-1.570796326794897+2.993222846126381*i", me.evaluate("asin(-10)"));
            assertEquals("-1.570796326794897+1.316957896924817*i", me.evaluate("asin(-2)"));
            assertEquals("-1.570796326794897", me.evaluate("asin(-1)"));
            assertEquals("0", me.evaluate("asin(0)"));
            assertEquals("1.570796326794897", me.evaluate("asin(1)"));
            assertEquals("1.570796326794897-1.316957896924817*i", me.evaluate("asin(2)"));
            assertEquals("1.570796326794897-2.993222846126381*i", me.evaluate("asin(10)"));

            assertEquals("Π-2.993222846126379*i", me.evaluate("acos(-10)"));
            assertEquals("Π-1.316957896924816*i", me.evaluate("acos(-2)"));
            assertEquals("Π", me.evaluate("acos(-1)"));
            assertEquals("1.570796326794897", me.evaluate("acos(0)"));
            assertEquals("0", me.evaluate("acos(1)"));
            assertEquals("1.316957896924816*i", me.evaluate("acos(2)"));
            assertEquals("2.993222846126379*i", me.evaluate("acos(10)"));

            assertEquals("-1.471127674303735", me.evaluate("atan(-10)"));
            assertEquals("-1.10714871779409", me.evaluate("atan(-2)"));
            assertEquals("-0.785398163397448", me.evaluate("atan(-1)"));
            assertEquals("0", me.evaluate("atan(0)"));
            assertEquals("0.785398163397448", me.evaluate("atan(1)"));
            assertEquals("1.10714871779409", me.evaluate("atan(2)"));
            assertEquals("1.471127674303735", me.evaluate("atan(10)"));

            for (int i = -10; i < 10; i++) {
                assertEquals(me.evaluate("3.14159265358979323846/2 - atan(" + i + ")"), me.evaluate("acot(" + i + ")"));
            }

            assertEquals("3.041924001098631", me.evaluate("3.14159265358979323846/2 - atan(-10)"));
            assertEquals("3.041924001098631", me.evaluate("acot(-10)"));
            assertEquals("1.570796326794897", me.evaluate("acot(0)"));
            assertEquals("2.677945044588987", me.evaluate("acot(-2)"));
            assertEquals("2.356194490192345", me.evaluate("acot(-1)"));
            assertEquals("0.785398163397448", me.evaluate("acot(1)"));
            assertEquals("0.463647609000806", me.evaluate("acot(2)"));
            assertEquals("0.099668652491162", me.evaluate("acot(10)"));

            assertEquals("Π", me.evaluate("π"));
            assertEquals("Π", me.evaluate("3.14159265358979323846"));
        } finally {
            me.setAngleUnits(AngleUnit.deg);
        }

        assertEquals("180", me.evaluate("Π"));
        assertEquals("180", me.evaluate("200-10%"));

        assertEquals("∞", me.evaluate("1/0"));
        assertEquals("-∞", me.evaluate("-1/0"));
        assertEquals("-∞", me.evaluate("-1/0"));
        assertEquals("∞", me.evaluate("(1 + 2) / (5 - 3 - 2)"));
        assertEquals("∞", me.evaluate("(1 + 2) / (5.1 - 3.1 - 2.0 )"));
        assertEquals("∞", me.evaluate("1/0"));
    }

    @Test
    public void testAngleUnits() throws Exception {
        final MathEngine mathEngine = JsclMathEngine.getInstance();

        final AngleUnit defaultAngleUnits = mathEngine.getAngleUnits();

        for (AngleUnit angleUnits : AngleUnit.values()) {
            try {
                mathEngine.setAngleUnits(angleUnits);
                mathEngine.evaluate("sin(2)");
                mathEngine.evaluate("asin(2)");
            } finally {
                mathEngine.setAngleUnits(defaultAngleUnits);
            }
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.rad);
            assertEquals("Π", mathEngine.evaluate("π"));
            assertEquals("π/2", mathEngine.simplify("π/2"));
            assertEquals(mathEngine.evaluate("0.9092974268256816953960198659117448427022549714478902683789"), mathEngine.evaluate("sin(2)"));
            assertEquals(mathEngine.evaluate("0.1411200080598672221007448028081102798469332642522655841518"), mathEngine.evaluate("sin(3)"));
            assertEquals(mathEngine.evaluate("0"), mathEngine.evaluate("sin(0)"));

            assertEquals(mathEngine.evaluate("1"), mathEngine.evaluate("cos(0)"));
            assertEquals(mathEngine.evaluate("0.8623188722876839341019385139508425355100840085355108292801"), mathEngine.evaluate("cos(100)"));
            assertEquals(mathEngine.evaluate("-0.416146836547142386997568229500762189766000771075544890755"), mathEngine.evaluate("cos(2)"));

            assertEquals(mathEngine.evaluate("-2.185039863261518991643306102313682543432017746227663164562"), mathEngine.evaluate("tan(2)"));
            assertEquals(mathEngine.evaluate("-0.142546543074277805295635410533913493226092284901804647633"), mathEngine.evaluate("tan(3)"));
            assertEquals(mathEngine.evaluate("0.6483608274590872"), mathEngine.evaluate("tan(10)"));

            assertEquals(mathEngine.evaluate("0.6420926159343306"), mathEngine.evaluate("cot(1)"));
            assertEquals(mathEngine.evaluate("-0.457657554360285763750277410432047276428486329231674329641"), mathEngine.evaluate("cot(2)"));
            assertEquals(mathEngine.evaluate("-7.015252551434533469428551379526476578293103352096353838156"), mathEngine.evaluate("cot(3)"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.deg);
            assertEquals(mathEngine.evaluate("0.9092974268256816953960198659117448427022549714478902683789"), mathEngine.evaluate("sin(deg(2))"));
            assertEquals(mathEngine.evaluate("0.1411200080598672221007448028081102798469332642522655841518"), mathEngine.evaluate("sin(deg(3))"));
            assertEquals(mathEngine.evaluate("0"), mathEngine.evaluate("sin(deg(0))"));

            assertEquals(mathEngine.evaluate("1"), mathEngine.evaluate("cos(deg(0))"));
            assertEquals(mathEngine.evaluate("0.8623188722876839341019385139508425355100840085355108292801"), mathEngine.evaluate("cos(deg(100))"));
            assertEquals(mathEngine.evaluate("-0.416146836547142386997568229500762189766000771075544890755"), mathEngine.evaluate("cos(deg(2))"));

            assertEquals(mathEngine.evaluate("-2.185039863261518991643306102313682543432017746227663164562"), mathEngine.evaluate("tan(deg(2))"));
            assertEquals(mathEngine.evaluate("-0.142546543074277805295635410533913493226092284901804647633"), mathEngine.evaluate("tan(deg(3))"));
            assertEquals(mathEngine.evaluate("0.6483608274590872"), mathEngine.evaluate("tan(deg(10))"));

            assertEquals(mathEngine.evaluate("0.6420926159343306"), mathEngine.evaluate("cot(deg(1))"));
            assertEquals(mathEngine.evaluate("-0.457657554360285763750277410432047276428486329231674329641"), mathEngine.evaluate("cot(deg(2))"));
            assertEquals(mathEngine.evaluate("-7.015252551434533469428551379526476578293103352096353838156"), mathEngine.evaluate("cot(deg(3))"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.rad);
            assertEquals(mathEngine.evaluate("-0.5235987755982989"), mathEngine.evaluate("asin(-0.5)"));
            assertEquals(mathEngine.evaluate("-0.47349551215005636"), mathEngine.evaluate("asin(-0.456)"));
            assertEquals(mathEngine.evaluate("0.32784124364198347"), mathEngine.evaluate("asin(0.322)"));

            assertEquals(mathEngine.evaluate("1.2429550831529133"), mathEngine.evaluate("acos(0.322)"));
            assertEquals(mathEngine.evaluate("1.5587960387762325"), mathEngine.evaluate("acos(0.012)"));
            assertEquals(mathEngine.evaluate("1.6709637479564563"), mathEngine.evaluate("acos(-0.1)"));

            assertEquals(mathEngine.evaluate("0.3805063771123649"), mathEngine.evaluate("atan(0.4)"));
            assertEquals(mathEngine.evaluate("0.09966865249116204"), mathEngine.evaluate("atan(0.1)"));
            assertEquals(mathEngine.evaluate("-0.5404195002705842"), mathEngine.evaluate("atan(-0.6)"));

            assertEquals(mathEngine.evaluate("1.0603080048781206"), mathEngine.evaluate("acot(0.56)"));
            // todo serso: wolfram alpha returns -0.790423 instead of 2.3511694068615325 (-PI)
            assertEquals(mathEngine.evaluate("2.3511694068615325"), mathEngine.evaluate("acot(-0.99)"));
            // todo serso: wolfram alpha returns -1.373401 instead of 1.7681918866447774 (-PI)
            assertEquals(mathEngine.evaluate("1.7681918866447774"), mathEngine.evaluate("acot(-0.2)"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.deg);
            assertEquals(mathEngine.evaluate("deg(-0.5235987755982989)"), mathEngine.evaluate("asin(-0.5)"));
            assertEquals(mathEngine.evaluate("-27.129294464583623"), mathEngine.evaluate("asin(-0.456)"));
            assertEquals(mathEngine.evaluate("18.783919611005786"), mathEngine.evaluate("asin(0.322)"));

            assertEquals(mathEngine.evaluate("71.21608038899423"), mathEngine.evaluate("acos(0.322)"));
            assertEquals(mathEngine.evaluate("89.31243414358914"), mathEngine.evaluate("acos(0.012)"));
            assertEquals(mathEngine.evaluate("95.73917047726678"), mathEngine.evaluate("acos(-0.1)"));

            assertEquals(mathEngine.evaluate("deg(0.3805063771123649)"), mathEngine.evaluate("atan(0.4)"));
            assertEquals(mathEngine.evaluate("deg(0.09966865249116204)"), mathEngine.evaluate("atan(0.1)"));
            assertEquals(mathEngine.evaluate("deg(-0.5404195002705842)"), mathEngine.evaluate("atan(-0.6)"));

            assertEquals(mathEngine.evaluate("deg(1.0603080048781206)"), mathEngine.evaluate("acot(0.56)"));
            // todo serso: wolfram alpha returns -0.790423 instead of 2.3511694068615325 (-PI)
            assertEquals(mathEngine.evaluate("134.7120839334429"), mathEngine.evaluate("acot(-0.99)"));
            // todo serso: wolfram alpha returns -1.373401 instead of 1.7681918866447774 (-PI)
            assertEquals(mathEngine.evaluate("deg(1.7681918866447774)"), mathEngine.evaluate("acot(-0.2)"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.deg);
            assertEquals(mathEngine.evaluate("0.0348994967025009716459951816253329373548245760432968714250"), mathEngine.evaluate("(sin(2))"));
            assertEquals(mathEngine.evaluate("0.0523359562429438327221186296090784187310182539401649204835"), mathEngine.evaluate("(sin(3))"));
            assertEquals(mathEngine.evaluate("0"), mathEngine.evaluate("sin(0)"));

            assertEquals(mathEngine.evaluate("1"), mathEngine.evaluate("cos(0)"));
            assertEquals(mathEngine.evaluate("-0.1736481776669303"), mathEngine.evaluate("(cos(100))"));
            assertEquals(mathEngine.evaluate("0.9993908270190958"), mathEngine.evaluate("(cos(2))"));

            assertEquals(mathEngine.evaluate("0.03492076949174773"), mathEngine.evaluate("(tan(2))"));
            assertEquals(mathEngine.evaluate("0.05240777928304121"), mathEngine.evaluate("(tan(3))"));
            assertEquals(mathEngine.evaluate("0.17632698070846498"), mathEngine.evaluate("(tan(10))"));

            assertEquals(mathEngine.evaluate("57.28996163075943"), mathEngine.evaluate("(cot(1))"));
            assertEquals(mathEngine.evaluate("28.636253282915604"), mathEngine.evaluate("(cot(2))"));
            assertEquals(mathEngine.evaluate("19.081136687728208"), mathEngine.evaluate("(cot(3))"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.rad);
            testSinEqualsToSinh(mathEngine, 0d);
            testSinEqualsToSinh(mathEngine, 1d, "0.841470984807897");
            testSinEqualsToSinh(mathEngine, 3d, "0.141120008059867");
            testSinEqualsToSinh(mathEngine, 6d);
            testSinEqualsToSinh(mathEngine, -1d, "-0.841470984807897");
            testSinEqualsToSinh(mathEngine, -3.3d, "0.157745694143248");
            testSinEqualsToSinh(mathEngine, -232.2d, "0.274294863736896");
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.deg);
            testSinEqualsToSinh(mathEngine, 0d);
            testSinEqualsToSinh(mathEngine, 1d, "0.017452406437284");
            testSinEqualsToSinh(mathEngine, 3d, "0.052335956242944");
            testSinEqualsToSinh(mathEngine, 6d, "0.104528463267653");
            testSinEqualsToSinh(mathEngine, -1d, "-0.017452406437284");
            testSinEqualsToSinh(mathEngine, -3.3d, "-0.057564026959567");
            testSinEqualsToSinh(mathEngine, -232.2d, "0.79015501237569");
            assertEquals("Π/2", mathEngine.simplify("Π/2"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

        try {
            mathEngine.setAngleUnits(AngleUnit.rad);
            assertEquals(mathEngine.evaluate("1.5707963267948966-0.8813735870195429*i"), mathEngine.evaluate("acos(i)"));
            assertEquals(mathEngine.evaluate("0.9045568943023814-1.0612750619050357*i"), mathEngine.evaluate("acos(1+i)"));
            assertEquals(mathEngine.evaluate("0.9999999999999999-0.9999999999999998*i"), mathEngine.evaluate("cos(acos(1-i))"));
            assertEquals(mathEngine.evaluate("-0.9045568943023814-1.0612750619050355*i"), mathEngine.evaluate("-acos(1-i)"));
        } finally {
            mathEngine.setAngleUnits(defaultAngleUnits);
        }

    }

    private void testSinEqualsToSinh(@Nonnull MathEngine mathEngine, @Nonnull Double x) throws ParseException {
        testSinEqualsToSinh(mathEngine, x, null);
    }

    private void testSinEqualsToSinh(@Nonnull MathEngine mathEngine, @Nonnull Double x, @Nullable String expected) throws ParseException {
        if (expected == null) {
            assertEquals(mathEngine.evaluate("sinh(i*" + x + ")/i"), mathEngine.evaluate("sin(" + x + ")"));
//			Assert.assertEquals(mathEngine.evaluate("exp("+x+")-sinh(" + x + ")"), mathEngine.evaluate("cosh(" + x + ")"));
        } else {
            assertEquals(expected, mathEngine.evaluate("sin(" + x + ")"));
            assertEquals(expected, mathEngine.evaluate("(exp(i * " + x + ") - cos(" + x + "))/i"));
            assertEquals(expected, mathEngine.evaluate("(exp(i * " + x + ") - cos(" + x + "))/i"));
        }
    }

    @Test
    public void testName() throws Exception {
        Expression.valueOf("a*c+b*sin(c)").toString();
    }

    @Test
    public void testIntegrals() throws Exception {
        assertEquals("50", Expression.valueOf("∫ab(x, x, 0, 10)").expand().numeric().toString());
        assertEquals("1/2*a^2", Expression.valueOf("∫ab(x, x, 0, a)").expand().toString());
        try {
            assertEquals("∫ab(x, x, 0)", Expression.valueOf("∫ab(x, x, 0)").expand().toString());
            fail();
        } catch (ParseException e) {
        }
        try {
            assertEquals("∫ab(x, x)", Expression.valueOf("∫ab(x, x)").expand().simplify().toString());
            fail();
        } catch (ParseException e) {
        }
        assertEquals("x^2/2", Expression.valueOf("∫(x, x)").expand().simplify().toString());
        try {
            assertEquals("x^2/2", Expression.valueOf("∫(x, x)").expand().numeric().toString());
            fail();
        } catch (ArithmeticException e) {
        }

        assertEquals("x^2/2", Expression.valueOf("∫(x, x)").expand().simplify().toString());
        assertEquals("ln(x)", Expression.valueOf("∫(1/x, x)").expand().simplify().toString());
        try {
            JsclMathEngine.getInstance().setAngleUnits(AngleUnit.rad);
            assertEquals("2*ln(2)+ln(cosh(x))", Expression.valueOf("∫(tanh(x), x)").expand().simplify().toString());
            assertEquals("2*ln(2)+ln(sin(x))", Expression.valueOf("∫(cot(x), x)").expand().simplify().toString());
            assertEquals("-2*ln(2)-ln(cos(x))", Expression.valueOf("∫(tan(x), x)").expand().simplify().toString());
        } finally {
            JsclMathEngine.getInstance().setAngleUnits(AngleUnit.deg);
        }
    }

    @Test
    public void testDerivations() throws Exception {
        final AngleUnit defaultAngleUnits = JsclMathEngine.getInstance().getAngleUnits();
        try {
            JsclMathEngine.getInstance().setAngleUnits(AngleUnit.rad);
            assertEquals("-0.909297426825682", Expression.valueOf("∂(cos(t),t,2)").numeric().toString());
            assertEquals("∂(cos(t), t, 2, 1)", Expression.valueOf("∂(cos(t),t,2)").simplify().toString());
            assertEquals("-2.234741690198506", Expression.valueOf("∂(t*cos(t),t,2)").numeric().toString());
            assertEquals("-4.469483380397012", Expression.valueOf("2*∂(t*cos(t),t,2)").numeric().toString());
            assertEquals("-sin(2)", Expression.valueOf("∂(cos(t),t,2)").expand().toString());
            assertEquals("-sin(t)", Expression.valueOf("∂(cos(t),t)").expand().toString());
            assertEquals("-sin(t)", Expression.valueOf("∂(cos(t),t,t,1)").expand().simplify().toString());
            assertEquals("∂(cos(t), t, t, 1°)", Expression.valueOf("∂(cos(t),t,t,1°)").expand().simplify().toString());
        } finally {
            JsclMathEngine.getInstance().setAngleUnits(defaultAngleUnits);
        }

        assertEquals("∂(cos(t), t, t, 1°)", Expression.valueOf("∂(cos(t),t,t,1°)").expand().numeric().toString());
    }

    @Test
    public void testSum() throws Exception {
        assertEquals("3", Expression.valueOf("Σ(n,n,1,2)").expand().toString());
        assertEquals("200", Expression.valueOf("Σ(n/n,n,1,200)").expand().toString());
        assertEquals("1/3", Expression.valueOf("Σ((n-1)/(n+1),n,1,2)").expand().toString());
        assertEquals("sin(1)", Expression.valueOf("Σ(sin(n),n,1,1)").expand().toString());
        assertEquals("1/1!", Expression.valueOf("Σ(n/n!,n,1,1)").expand().toString());
        assertEquals("2", Expression.valueOf("Σ(n/n!,n,1,2)").expand().numeric().toString());
        assertEquals("2.718281828459046", Expression.valueOf("Σ(n/n!,n,1,200)").expand().numeric().toString());
        assertEquals("2.718281828459046", Expression.valueOf("Σ(n/(2*n/2)!,n,1,200)").expand().numeric().toString());
        assertEquals(Expression.valueOf("3").numeric().toString(), Expression.valueOf("Σ(n°,n,1,2)").expand().numeric().toString());
        assertEquals("200", Expression.valueOf("Σ(n°/n°,n,1,200)").expand().numeric().toString());
        assertEquals("-sin(1)-sin(2)", Expression.valueOf("Σ(∂(cos(t),t,n),n,1,2)").expand().toString());
        assertEquals("-0.052351903139784", Expression.valueOf("Σ(∂(cos(t),t,n),n,1,2)").expand().numeric().toString());
    }

    @Test
    public void testNumeralBases() throws Exception {
        final JsclMathEngine me = JsclMathEngine.getInstance();
        //final NumeralBase defaultNumeralBase = me.getDefaultNumeralBase();
        try {
            //me.setDefaultNumeralBase(NumeralBase.bin);
            assertEquals("10", me.evaluate("0b:01010"));
            assertEquals("10", me.evaluate("0b:1010"));
            assertEquals("520", me.evaluate("0o:1010"));
            assertEquals("1010", me.evaluate("1010"));
            assertEquals("1010.1", me.evaluate("1010.1"));
        } finally {
            //me.setDefaultNumeralBase(defaultNumeralBase);
        }

        try {
            me.setNumeralBase(NumeralBase.hex);
            assertEquals("22F", me.evaluate("22F*exp(F)/exp(F)"));
            assertEquals("E", me.evaluate("E"));
        } finally {
            me.setNumeralBase(NumeralBase.dec);

        }
    }

    @Test
    public void testFormat() throws Exception {
        final JsclMathEngine me = JsclMathEngine.getInstance();
        try {
            me.setGroupingSeparator(' ');
            assertEquals("123 456.7891011", Expression.valueOf("123456.7891011").numeric().toString());
            assertEquals("123 456.7891011", Expression.valueOf("123456.7891011").simplify().toString());
            assertEquals("123 456.7891011123", Expression.valueOf("123456.7891011123123123123123").simplify().toString());
            assertEquals("0.000001222", Expression.valueOf("1222/(10^9)").numeric().toString());
            assertEquals("12 345", JsclInteger.valueOf(12345L).toString());

            me.setNotation(Real.NumberFormat.FSE_SCI);
            assertEquals("0", Expression.valueOf("0.0").simplify().toString());
            assertEquals("1", Expression.valueOf("1.0").simplify().toString());
            assertEquals("100", Expression.valueOf("100.0").simplify().toString());

            me.setNotation(Real.NumberFormat.FSE_NONE);
            me.setPrecision(5);
            assertEquals("0", Expression.valueOf("1222/(10^9)").numeric().toString());

            me.setNotation(Real.NumberFormat.FSE_SCI);
            me.setPrecision(5);
            assertEquals("1.222E-6", Expression.valueOf("1222/(10^9)").numeric().toString());

            me.setPrecision(10);
            assertEquals("1.222E-6", Expression.valueOf("1222/(10^9)").numeric().toString());

            me.setPrecision(NumberFormatter.MAX_PRECISION);
            assertEquals("1.222E-6", Expression.valueOf("1222/(10^9)").numeric().toString());

            me.setNotation(Real.NumberFormat.FSE_NONE);
            assertEquals("0.333333333333333", Expression.valueOf("1/3").numeric().toString());

            me.setNotation(Real.NumberFormat.FSE_SCI);
            assertEquals("0.333333333333333", Expression.valueOf("1/3").numeric().toString());

            me.setPrecision(10);
            assertEquals("0.3333333333", Expression.valueOf("1/3").numeric().toString());

            me.setNotation(Real.NumberFormat.FSE_NONE);
            me.setPrecision(10);
            assertEquals("0.3333333333", Expression.valueOf("1/3").numeric().toString());

        } finally {
            me.setGroupingSeparator(NumberFormatter.NO_GROUPING);
            me.setNotation(Real.NumberFormat.FSE_NONE);
            me.setPrecision(NumberFormatter.MAX_PRECISION);
        }
    }
}
