/*
 * 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.
 */
package org.apache.lucene.spatial3d.geom;

import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;

public class XYZSolidTest extends LuceneTestCase {

  @Test
  public void testNonDegenerateRelationships() {
    XYZSolid s;
    GeoShape shape;
    // Something bigger than the world
    s = new StandardXYZSolid(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
    // Any shape, except whole world, should be within.
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    // An XYZSolid represents a surface shape, which when larger than the world is in fact
    // the entire world, so it should overlap the world.
    assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));

    // Something overlapping the world on only one side
    s = new StandardXYZSolid(PlanetModel.SPHERE, -2.0, 0.0, -2.0, 2.0, -2.0, 2.0);
    // Some things should be disjoint...
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
    // And, some things should be within... 
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
    assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
    // And, some things should overlap.
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));

    // Partial world should be contained by GeoWorld object...
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, s.getRelationship(shape));
    
    // Something inside the world
    s = new StandardXYZSolid(PlanetModel.SPHERE, -0.1, 0.1, -0.1, 0.1, -0.1, 0.1);
    // All shapes should be disjoint
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
    
  }

  @Test
  public void testDegenerateRelationships() {
    GeoArea solid;
    GeoShape shape;
    
    // Basic test of the factory method - non-degenerate
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
    // Any shape, except whole world, should be within.
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.WITHIN, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    // An XYZSolid represents a surface shape, which when larger than the world is in fact
    // the entire world, so it should overlap the world.
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));

    // Build a degenerate point, not on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    // disjoint with everything?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a degenerate point that IS on the sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0);
    // inside everything that it touches?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape degenerate in (x,y), which has no points on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 0.1);
    // disjoint with everything?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape degenerate in (x,y) which has one point on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 1.1);
    // inside everything that it touches?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape degenerate in (x,y) which has two points on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -1.1, 1.1);
    // inside everything that it touches?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    
    // Build a shape degenerate in (x,z), which has no points on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 0.0, 0.0);
    // disjoint with everything?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape degenerate in (x,z) which has one point on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 1.1, 0.0, 0.0);
    // inside everything that it touches?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape degenerate in (x,y) which has two points on sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, 0.0, 0.0);
    // inside everything that it touches?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));

    // MHL for y-z check
    
    // Build a shape that is degenerate in x, which has zero points intersecting sphere
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, -0.1, 0.1);
    // disjoint with everything?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape that is degenerate in x, which has zero points intersecting sphere, second variation
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 1.1, 1.2);
    // disjoint with everything?
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));

    // Build a shape that is disjoint in X but intersects sphere in a complete circle
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, -1.1, 1.1);
    // inside everything that it touches?
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));

    // Build a shape that is disjoint in X but intersects sphere in a half circle in Y
    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 1.1, -1.1, 1.1);
    // inside everything that it touches?
    shape = new GeoWorld(PlanetModel.SPHERE);
    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));

    // MHL for degenerate Y
    // MHL for degenerate Z
    
  }

  @Test
  //@AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/LUCENE-8457")
  public void testLUCENE8457() {
    GeoShape shape = GeoBBoxFactory.makeGeoBBox(PlanetModel.WGS84, Math.PI, 1.2487354264870392, 0.0, 3.5181789305199657E-12);
    //System.out.println("shape = "+shape);
    XYZBounds bounds = new XYZBounds();
    shape.getBounds(bounds);
    XYZSolid solid = XYZSolidFactory.makeXYZSolid(PlanetModel.WGS84, bounds);
    //System.out.println("solid = "+solid);

    GeoPoint point = new GeoPoint(PlanetModel.WGS84, 1.4812439919751819, -3.141592653589793);
    //System.out.println("point="+point);
    //if the point is within the shape, it must be within the solid
    if (shape.isWithin(point)) {
      assertTrue(solid.isWithin(point));
    }

  }
  
}
