/*
 * 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;

/**
 * Factory for {@link GeoBBox}.
 *
 * @lucene.experimental
 */
public class GeoBBoxFactory {
  private GeoBBoxFactory() {
  }

  /**
   * Create a geobbox of the right kind given the specified bounds.
   *
   * @param planetModel is the planet model
   * @param topLat    is the top latitude
   * @param bottomLat is the bottom latitude
   * @param leftLon   is the left longitude
   * @param rightLon  is the right longitude
   * @return a GeoBBox corresponding to what was specified.
   */
  public static GeoBBox makeGeoBBox(final PlanetModel planetModel, double topLat, double bottomLat, double leftLon, double rightLon) {
    //System.err.println("Making rectangle for topLat="+topLat*180.0/Math.PI+", bottomLat="+bottomLat*180.0/Math.PI+", leftLon="+leftLon*180.0/Math.PI+", rightlon="+rightLon*180.0/Math.PI);
    if (topLat > Math.PI * 0.5)
      topLat = Math.PI * 0.5;
    if (bottomLat < -Math.PI * 0.5)
      bottomLat = -Math.PI * 0.5;
    if (leftLon < -Math.PI)
      leftLon = -Math.PI;
    if (rightLon > Math.PI)
      rightLon = Math.PI;
    if ((Math.abs(leftLon + Math.PI) < Vector.MINIMUM_ANGULAR_RESOLUTION && Math.abs(rightLon - Math.PI) < Vector.MINIMUM_ANGULAR_RESOLUTION) ||
        (Math.abs(rightLon + Math.PI) < Vector.MINIMUM_ANGULAR_RESOLUTION && Math.abs(leftLon - Math.PI) < Vector.MINIMUM_ANGULAR_RESOLUTION)) {
      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION && Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION)
        return new GeoWorld(planetModel);
      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION)
          return new GeoDegeneratePoint(planetModel, topLat, 0.0);
        return new GeoDegenerateLatitudeZone(planetModel, topLat);
      }
      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION)
        return new GeoNorthLatitudeZone(planetModel, bottomLat);
      else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION)
        return new GeoSouthLatitudeZone(planetModel, topLat);
      return new GeoLatitudeZone(planetModel, topLat, bottomLat);
    }
    //System.err.println(" not latitude zone");
    double extent = rightLon - leftLon;
    if (extent < 0.0)
      extent += Math.PI * 2.0;
    if (topLat == Math.PI * 0.5 && bottomLat == -Math.PI * 0.5) {
      if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_ANGULAR_RESOLUTION)
        return new GeoDegenerateLongitudeSlice(planetModel, leftLon);

      if (extent >= Math.PI)
        return new GeoWideLongitudeSlice(planetModel, leftLon, rightLon);

      return new GeoLongitudeSlice(planetModel, leftLon, rightLon);
    }
    //System.err.println(" not longitude slice");
    if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION)
        return new GeoDegeneratePoint(planetModel, topLat, leftLon);
      return new GeoDegenerateVerticalLine(planetModel, topLat, bottomLat, leftLon);
    }
    //System.err.println(" not vertical line");
    if (extent >= Math.PI) {
      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
          return new GeoDegeneratePoint(planetModel, topLat, 0.0);
        } else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
          return new GeoDegeneratePoint(planetModel, bottomLat, 0.0);
        }
        //System.err.println(" wide degenerate line");
        return new GeoWideDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);
      }
      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        return new GeoWideNorthRectangle(planetModel, bottomLat, leftLon, rightLon);
      } else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        return new GeoWideSouthRectangle(planetModel, topLat, leftLon, rightLon);
      }
      //System.err.println(" wide rect");
      return new GeoWideRectangle(planetModel, topLat, bottomLat, leftLon, rightLon);
    }
    if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        return new GeoDegeneratePoint(planetModel, topLat, 0.0);
      } else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
        return new GeoDegeneratePoint(planetModel, bottomLat, 0.0);
      }
      //System.err.println(" horizontal line");
      return new GeoDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);
    }
    if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_ANGULAR_RESOLUTION) {
      return new GeoNorthRectangle(planetModel, bottomLat, leftLon, rightLon);
    } else if (Math.abs(bottomLat + Math.PI * 0.5) <  Vector.MINIMUM_ANGULAR_RESOLUTION) {
      return new GeoSouthRectangle(planetModel, topLat, leftLon, rightLon);
    }
    //System.err.println(" rectangle");
    return new GeoRectangle(planetModel, topLat, bottomLat, leftLon, rightLon);
  }

  /**
   * Create a geobbox of the right kind given the specified {@link LatLonBounds}.
   *
   * @param planetModel is the planet model
   * @param bounds    are the bounds
   * @return a GeoBBox corresponding to what was specified.
   */
  public static GeoBBox makeGeoBBox(final PlanetModel planetModel, LatLonBounds bounds) {
    final double topLat = (bounds.checkNoTopLatitudeBound()) ? Math.PI * 0.5 : bounds.getMaxLatitude();
    final double bottomLat = (bounds.checkNoBottomLatitudeBound()) ? -Math.PI * 0.5 : bounds.getMinLatitude();
    final double leftLon = (bounds.checkNoLongitudeBound()) ? -Math.PI : bounds.getLeftLongitude();
    final double rightLon = (bounds.checkNoLongitudeBound()) ? Math.PI : bounds.getRightLongitude();
    return makeGeoBBox(planetModel, topLat, bottomLat, leftLon, rightLon);
  }

}
