package gw.accountlocation

uses com.guidewire.pl.system.locale.DisplayKey
uses gw.api.database.IQueryBeanResult
uses gw.api.database.Query
uses java.lang.IllegalArgumentException
uses java.math.BigDecimal
uses com.guidewire.pl.domain.geodata.proximitysearch.ProximitySearchWithWideningRadius
uses gw.api.database.spatial.SpatialPoint
uses gw.api.database.PropertyResolver
uses gw.api.system.PLConfigParameters

/**
 * This interface provides methods for querying the database for {@link AccountLocation}s.
 */
@ReadOnly
public class AccountLocationFinder {
  /**
   * Performs a radius search based on the passed parameters.  In a radius search, all account locations within
   * a certain distance from a given search center will be returned.
   *
   * @param center Center of the search (this address must be geocoded).
   * @param radius Radius of the search (this value must be positive).
   * @param units  Units of measurement for the <code>radius</code>.
   * @return A query result containing account locations within the given <code>radius</code> (in terms of the passed
   *         <code>units</code> of distance) from the given <code>center</code>.
   */
  @Throws(IllegalArgumentException,
        "If any of the parameters are <code>null</code>, the <code>radius</code> is not a positive value " +
        "or the <code>center</code> is not sufficiently geocoded for the search.")
  public function findLocationsWithinRadius(center : Address,
                                            radius : int,
                                            units : UnitOfDistance) : IQueryBeanResult {
    var query = Query.make(AccountLocation)
    assertAddress( center )
    var narrowestRadius = ProximitySearchWithWideningRadius.findNarrowestRadius(query, PropertyResolver.getProperty(Address, "SpatialPoint"),
        center.SpatialPoint, units, radius, PLConfigParameters.ProximityRadiusSearchDefaultMaxResultCount.Value)
    query.withinDistance("SpatialPoint", center.SpatialPoint, narrowestRadius, units)
    return query.select()
  }

  /**
   * Performs a radius search based on the passed parameters.  In a radius search, all account locations within
   * a certain distance from a given search center will be returned.
   *
   * @param latitude  Latitude of the center of the search.
   * @param longitude Longitude of the center of the search.
   * @param radius    Radius of the search (this value must be positive).
   * @param units     Units of measurement for the <code>radius</code>.
   * @return A query result containing account locations within the given <code>radius</code> (in terms of the passed
   *         <code>units</code> of distance) from the given <code>center</code> (expressed as a <code>latitude</code> /
   *         <code>longitude</code> combination).
   */
  @Throws(IllegalArgumentException,
        "If any of the parameters are <code>null</code> or the <code>radius</code> is not a positive value.")
  public function findLocationsWithinRadiusLatLong(latitude : BigDecimal,
                                                   longitude : BigDecimal,
                                                   radius : int,
                                                   units : UnitOfDistance) : IQueryBeanResult {
    var query = Query.make(AccountLocation)
    var center = new SpatialPoint(longitude, latitude)
    var narrowestRadius = ProximitySearchWithWideningRadius.findNarrowestRadius(query, PropertyResolver.getProperty(Address, "SpatialPoint"),
        center, units, radius, PLConfigParameters.ProximityRadiusSearchDefaultMaxResultCount.Value)
    query.withinDistance("SpatialPoint", center, narrowestRadius, units)
    return query.select()
  }

  /**
   * Performs an ordinal search based on the passed parameters.  In an ordinal search, the closest <code>n</code>
   * account locations to a given search center will be returned.
   *
   * @param center   Center of the search (this address must be geocoded).
   * @param ordinal  Number of results to include in search (this value must be positive).
   * @return A query result containing at least the closest <code>n</code> account locations from the given center. More than <code>n</code> account locations may be returned.
   */
  @Throws(IllegalArgumentException,
        "If <code>center</code> is <code>null</code>, <code>ordinal</code> is not a positive " +
        "value, or the <code>center</code> is not sufficiently geocoded for the search.")
  public function findClosestNLocations(center : Address, ordinal : int) : IQueryBeanResult {
    var query = Query.make(AccountLocation)
    assertAddress( center )
    var units = PLConfigParameters.UseMetricDistancesByDefault.Value ? UnitOfDistance.TC_KILOMETER : UnitOfDistance.TC_MILE
    var narrowestRadius = ProximitySearchWithWideningRadius.findNarrowestRadius(query, PropertyResolver.getProperty(Address, "SpatialPoint"),
        center.SpatialPoint, units, PLConfigParameters.ProximitySearchOrdinalMaxDistance.Value, ordinal)
    query.withinDistance("SpatialPoint", center.SpatialPoint, narrowestRadius, units)
    return query.select()
  }

  /**
   * Performs an ordinal search based on the passed parameters.  In an ordinal search, the closest <code>n</code>
   * account locations to a given search center will be returned.
   *
   * @param latitude  Latitude of the center of the search.
   * @param longitude Longitude of the center of the search.
   * @param ordinal   Number of results to include in search (this value must be positive).
   * @return A query result containing at least the closest <code>n</code> account locations from the given center
   *         (expressed as a <code>latitude</code> / <code>longitude</code> combination). More than <code>n</code> account locations may be returned.
   */
  @Throws(IllegalArgumentException,
        "If any of the parameters are <code>null</code> or <code>ordinal</code> is not a positive value.")
  public function findClosestNLocationsLatLong(latitude : BigDecimal,
                                               longitude : BigDecimal,
                                               ordinal : int) : IQueryBeanResult {
    var query = Query.make(AccountLocation)
    var center = new SpatialPoint(longitude, latitude)
    var units = PLConfigParameters.UseMetricDistancesByDefault.Value ? UnitOfDistance.TC_KILOMETER : UnitOfDistance.TC_MILE
    var narrowestRadius = ProximitySearchWithWideningRadius.findNarrowestRadius(query, PropertyResolver.getProperty(Address, "SpatialPoint"),
        center, units, PLConfigParameters.ProximitySearchOrdinalMaxDistance.Value, ordinal)
    query.withinDistance("SpatialPoint", center, narrowestRadius, units)
    return query.select()
  }
  
  private static function assertAddress( address : Address ) {
    // assert address
    assertNonNullArgument( address, new DisplayKey("Java.ProximitySearch.Argument.Center").localize() )
    // assert address is successfully geocoded
    if ( !address.isSuccessfullyGeocoded() ) {
      throw new IllegalArgumentException( new DisplayKey("Java.ProximitySearch.Error.NonGeocodedCenter").localize() );
    }
    // assert geocode status
    assertNonNullArgument(address.GeocodeStatus, new DisplayKey("Java.ProximitySearch.Argument.GeocodeStatus").localize())
  }
  
  private static function assertNonNullArgument( argument : Object, argumentName : String) {
    if ( argument == null ) {
       throw new IllegalArgumentException( new DisplayKey("Java.ProximitySearch.Error.NullArgument").localize({argumentName}) )
    }    
  }
    
}