using Lucene.Net.Spatial.Prefix.Tree;
using Lucene.Net.Spatial.Queries;
using NUnit.Framework;
using Spatial4n.Core.Context;
using Spatial4n.Core.Distance;
using Spatial4n.Core.Shapes;
using System.Collections.Generic;
using System.Globalization;

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

    public class TestRecursivePrefixTreeStrategy : StrategyTestCase
    {
        private int maxLength;

        //Tests should call this first.
        private void init(int maxLength)
        {
            this.maxLength = maxLength;
            this.ctx = SpatialContext.GEO;
            GeohashPrefixTree grid = new GeohashPrefixTree(ctx, maxLength);
            this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
        }

        [Test]
        public virtual void TestFilterWithVariableScanLevel()
        {
            init(GeohashPrefixTree.MaxLevelsPossible);
            getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);

            //execute queries for each prefix grid scan level
            for (int i = 0; i <= maxLength; i++)
            {
                ((RecursivePrefixTreeStrategy)strategy).PrefixGridScanLevel = (i);
                executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
            }
        }

        [Test]
        public virtual void TestOneMeterPrecision()
        {
            init(GeohashPrefixTree.MaxLevelsPossible);
            GeohashPrefixTree grid = (GeohashPrefixTree)((RecursivePrefixTreeStrategy)strategy).Grid;
            //DWS: I know this to be true.  11 is needed for one meter
            double degrees = DistanceUtils.Dist2Degrees(0.001, DistanceUtils.EARTH_MEAN_RADIUS_KM);
            assertEquals(11, grid.GetLevelForDistance(degrees));
        }

        [Test]
        public virtual void TestPrecision()
        {
            init(GeohashPrefixTree.MaxLevelsPossible);

            Spatial4n.Core.Shapes.IPoint iPt = ctx.MakePoint(2.8028712999999925, 48.3708044);//lon, lat
            AddDocument(newDoc("iPt", iPt));
            Commit();

            Spatial4n.Core.Shapes.IPoint qPt = ctx.MakePoint(2.4632387000000335, 48.6003516);

            double KM2DEG = DistanceUtils.Dist2Degrees(1, DistanceUtils.EARTH_MEAN_RADIUS_KM);
            double DEG2KM = 1 / KM2DEG;

            double DIST = 35.75;//35.7499...
            assertEquals(DIST, ctx.DistCalc.Distance(iPt, qPt) * DEG2KM, 0.001);

            //distErrPct will affect the query shape precision. The indexed precision
            // was set to nearly zilch via init(GeohashPrefixTree.getMaxLevelsPossible());
            double distErrPct = 0.025; //the suggested default, by the way
            double distMult = 1 + distErrPct;

            assertTrue(35.74 * distMult >= DIST);
            checkHits(q(qPt, 35.74 * KM2DEG, distErrPct), 1, null);

            assertTrue(30 * distMult < DIST);
            checkHits(q(qPt, 30 * KM2DEG, distErrPct), 0, null);

            assertTrue(33 * distMult < DIST);
            checkHits(q(qPt, 33 * KM2DEG, distErrPct), 0, null);

            assertTrue(34 * distMult < DIST);
            checkHits(q(qPt, 34 * KM2DEG, distErrPct), 0, null);
        }

        private SpatialArgs q(Spatial4n.Core.Shapes.IPoint pt, double distDEG, double distErrPct)
        {
            IShape shape = ctx.MakeCircle(pt, distDEG);
            SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);
            args.DistErrPct = (distErrPct);
            return args;
        }

        private void checkHits(SpatialArgs args, int assertNumFound, int[] assertIds)
        {
            SearchResults got = executeQuery(strategy.MakeQuery(args), 100);
            assertEquals("" + args, assertNumFound, got.numFound);
            if (assertIds != null)
            {
                ISet<int?> gotIds = new HashSet<int?>();
                foreach (SearchResult result in got.results)
                {
                    gotIds.add(int.Parse(result.document.Get("id"), CultureInfo.InvariantCulture));
                }
                foreach (int assertId in assertIds)
                {
                    assertTrue("has " + assertId, gotIds.contains(assertId));
                }
            }
        }
    }
}
