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

using System;
using System.Text;
using Spatial4n.Core.Context;
using Spatial4n.Core.Exceptions;
using Spatial4n.Core.Shapes;

namespace Spatial4n.Core.Exceptions
{
	[Serializable]
	public class InvalidSpatialArgument : ArgumentException
	{
		public InvalidSpatialArgument(String reason)
			: base(reason)
		{
		}
	}
}

namespace Lucene.Net.Spatial.Queries
{
	public class SpatialArgs
	{
		public static readonly double DEFAULT_DISTERRPCT = 0.025d;

		public SpatialOperation Operation { get; set; }

	    public SpatialArgs(SpatialOperation operation, Shape shape)
		{
            if (operation == null || shape == null)
                throw new ArgumentException("operation and shape are required");
			this.Operation = operation;
			this.Shape = shape;
		}

        /// <summary>
        /// Computes the distance given a shape and the {@code distErrPct}.  The
        /// algorithm is the fraction of the distance from the center of the query
        /// shape to its furthest bounding box corner.
        /// </summary>
        /// <param name="shape">Mandatory.</param>
        /// <param name="distErrPct">0 to 0.5</param>
        /// <param name="ctx">Mandatory</param>
        /// <returns>A distance (in degrees).</returns>
        public static double CalcDistanceFromErrPct(Shape shape, double distErrPct, SpatialContext ctx)
        {
            if (distErrPct < 0 || distErrPct > 0.5)
            {
                throw new ArgumentException("distErrPct " + distErrPct + " must be between [0 to 0.5]", "distErrPct");
            }
            if (distErrPct == 0 || shape is Point)
            {
                return 0;
            }
            Rectangle bbox = shape.GetBoundingBox();
            //The diagonal distance should be the same computed from any opposite corner,
            // and this is the longest distance that might be occurring within the shape.
            double diagonalDist = ctx.GetDistCalc().Distance(
                ctx.MakePoint(bbox.GetMinX(), bbox.GetMinY()), bbox.GetMaxX(), bbox.GetMaxY());
            return diagonalDist*0.5*distErrPct;
        }

        /// <summary>
        /// Gets the error distance that specifies how precise the query shape is. This
        /// looks at {@link #getDistErr()}, {@link #getDistErrPct()}, and {@code
        /// defaultDistErrPct}.
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="defaultDistErrPct">0 to 0.5</param>
        /// <returns>>= 0</returns>
        public double ResolveDistErr(SpatialContext ctx, double defaultDistErrPct)
        {
            if (DistErr != null)
                return DistErr.Value;
            double? distErrPct = (this.distErrPct ?? defaultDistErrPct);
            return CalcDistanceFromErrPct(Shape, distErrPct.Value, ctx);
        }

	    /// <summary>
		/// Check if the arguments make sense -- throw an exception if not
		/// </summary>
		public void Validate()
		{
			if (Operation.IsTargetNeedsArea() && !Shape.HasArea())
			{
                throw new ArgumentException(Operation + " only supports geometry with area");
			}
		}

		public override String ToString()
		{
            return SpatialArgsParser.WriteSpatialArgs(this);
		}

		//------------------------------------------------
		// Getters & Setters
		//------------------------------------------------

	    public Shape Shape { get; set; }

	    /// <summary>
	    /// A measure of acceptable error of the shape as a fraction. This effectively
	    /// inflates the size of the shape but should not shrink it.
	    /// <p/>
	    /// The default is {@link #DEFAULT_DIST_PRECISION}
	    /// </summary>
	    /// <returns>0 to 0.5</returns>
	    public double? DistErrPct
	    {
	        get { return distErrPct; }
	        set
	        {
	            if (value != null)
	                distErrPct = value.Value;
	        }
	    }
        private double? distErrPct;

	    /// <summary>
        /// The acceptable error of the shape.  This effectively inflates the
        /// size of the shape but should not shrink it.
        /// </summary>
        /// <returns>>= 0</returns>
	    public double? DistErr { get; set; }
	}
}
