using NUnit.Framework;
using RandomizedTesting.Generators;
using System;
using Assert = Lucene.Net.TestFramework.Assert;

namespace Lucene.Net.Util
{
    /*
     * 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.
     */

    [TestFixture]
    public class TestSloppyMath : LuceneTestCase
    {
        // accuracy for cos()
        internal static double COS_DELTA = 1E-15;

        // accuracy for asin()
        internal static double ASIN_DELTA = 1E-7;

        [Test]
        public virtual void TestCos()
        {
            Assert.IsTrue(double.IsNaN(Math.Cos(double.NaN)));
            Assert.IsTrue(double.IsNaN(Math.Cos(double.NegativeInfinity)));
            Assert.IsTrue(double.IsNaN(Math.Cos(double.PositiveInfinity)));
            Assert.AreEqual(Math.Cos(1), Math.Cos(1), COS_DELTA);
            Assert.AreEqual(Math.Cos(0), Math.Cos(0), COS_DELTA);
            Assert.AreEqual(Math.Cos(Math.PI / 2), Math.Cos(Math.PI / 2), COS_DELTA);
            Assert.AreEqual(Math.Cos(-Math.PI / 2), Math.Cos(-Math.PI / 2), COS_DELTA);
            Assert.AreEqual(Math.Cos(Math.PI / 4), Math.Cos(Math.PI / 4), COS_DELTA);
            Assert.AreEqual(Math.Cos(-Math.PI / 4), Math.Cos(-Math.PI / 4), COS_DELTA);
            Assert.AreEqual(Math.Cos(Math.PI * 2 / 3), Math.Cos(Math.PI * 2 / 3), COS_DELTA);
            Assert.AreEqual(Math.Cos(-Math.PI * 2 / 3), Math.Cos(-Math.PI * 2 / 3), COS_DELTA);
            Assert.AreEqual(Math.Cos(Math.PI / 6), Math.Cos(Math.PI / 6), COS_DELTA);
            Assert.AreEqual(Math.Cos(-Math.PI / 6), Math.Cos(-Math.PI / 6), COS_DELTA);

            // testing purely random longs is inefficent, as for stupid parameters we just
            // pass thru to Math.cos() instead of doing some huperduper arg reduction
            for (int i = 0; i < 10000; i++)
            {
                double d = Random.NextDouble() * SloppyMath.SIN_COS_MAX_VALUE_FOR_INT_MODULO;
                if (Random.NextBoolean())
                {
                    d = -d;
                }
                Assert.AreEqual(Math.Cos(d), Math.Cos(d), COS_DELTA);
            }
        }

        [Test]
        public virtual void TestAsin()
        {
            Assert.IsTrue(double.IsNaN(Math.Asin(double.NaN)));
            Assert.IsTrue(double.IsNaN(Math.Asin(2)));
            Assert.IsTrue(double.IsNaN(Math.Asin(-2)));
            Assert.AreEqual(-Math.PI / 2, Math.Asin(-1), ASIN_DELTA);
            Assert.AreEqual(-Math.PI / 3, Math.Asin(-0.8660254), ASIN_DELTA);
            Assert.AreEqual(-Math.PI / 4, Math.Asin(-0.7071068), ASIN_DELTA);
            Assert.AreEqual(-Math.PI / 6, Math.Asin(-0.5), ASIN_DELTA);
            Assert.AreEqual(0, Math.Asin(0), ASIN_DELTA);
            Assert.AreEqual(Math.PI / 6, Math.Asin(0.5), ASIN_DELTA);
            Assert.AreEqual(Math.PI / 4, Math.Asin(0.7071068), ASIN_DELTA);
            Assert.AreEqual(Math.PI / 3, Math.Asin(0.8660254), ASIN_DELTA);
            Assert.AreEqual(Math.PI / 2, Math.Asin(1), ASIN_DELTA);
            // only values -1..1 are useful
            for (int i = 0; i < 10000; i++)
            {
                double d = Random.NextDouble();
                if (Random.NextBoolean())
                {
                    d = -d;
                }
                Assert.AreEqual(Math.Asin(d), Math.Asin(d), ASIN_DELTA);
                Assert.IsTrue(Math.Asin(d) >= -Math.PI / 2);
                Assert.IsTrue(Math.Asin(d) <= Math.PI / 2);
            }
        }

        [Test]
        public virtual void TestHaversin()
        {
            Assert.IsTrue(double.IsNaN(SloppyMath.Haversin(1, 1, 1, double.NaN)));
            Assert.IsTrue(double.IsNaN(SloppyMath.Haversin(1, 1, double.NaN, 1)));
            Assert.IsTrue(double.IsNaN(SloppyMath.Haversin(1, double.NaN, 1, 1)));
            Assert.IsTrue(double.IsNaN(SloppyMath.Haversin(double.NaN, 1, 1, 1)));

            Assert.AreEqual(0, SloppyMath.Haversin(0, 0, 0, 0), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(0, -180, 0, -180), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(0, -180, 0, 180), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(0, 180, 0, 180), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(90, 0, 90, 0), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(90, -180, 90, -180), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(90, -180, 90, 180), 0D);
            Assert.AreEqual(0, SloppyMath.Haversin(90, 180, 90, 180), 0D);

            // Test half a circle on the equator, using WGS84 earth radius
            double earthRadiusKMs = 6378.137;
            double halfCircle = earthRadiusKMs * Math.PI;
            Assert.AreEqual(halfCircle, SloppyMath.Haversin(0, 0, 0, 180), 0D);

            Random r = Random;
            double randomLat1 = 40.7143528 + (r.Next(10) - 5) * 360;
            double randomLon1 = -74.0059731 + (r.Next(10) - 5) * 360;

            double randomLat2 = 40.65 + (r.Next(10) - 5) * 360;
            double randomLon2 = -73.95 + (r.Next(10) - 5) * 360;

            Assert.AreEqual(8.572, SloppyMath.Haversin(randomLat1, randomLon1, randomLat2, randomLon2), 0.01D);

            // from solr and ES tests (with their respective epsilons)
            Assert.AreEqual(0, SloppyMath.Haversin(40.7143528, -74.0059731, 40.7143528, -74.0059731), 0D);
            Assert.AreEqual(5.286, SloppyMath.Haversin(40.7143528, -74.0059731, 40.759011, -73.9844722), 0.01D);
            Assert.AreEqual(0.4621, SloppyMath.Haversin(40.7143528, -74.0059731, 40.718266, -74.007819), 0.01D);
            Assert.AreEqual(1.055, SloppyMath.Haversin(40.7143528, -74.0059731, 40.7051157, -74.0088305), 0.01D);
            Assert.AreEqual(1.258, SloppyMath.Haversin(40.7143528, -74.0059731, 40.7247222, -74), 0.01D);
            Assert.AreEqual(2.029, SloppyMath.Haversin(40.7143528, -74.0059731, 40.731033, -73.9962255), 0.01D);
            Assert.AreEqual(8.572, SloppyMath.Haversin(40.7143528, -74.0059731, 40.65, -73.95), 0.01D);
        }
    }
}
