/*
 * @(#)Metric.java    2.4.0   7 September 2005
 *
 * Copyright 2005
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.util;

import java.awt.geom.*;

/**
 * <p>The abstract class <code>Metric</code> define the requirement
 * for a 2-dimensional distance measure or metric and includes
 * methods to test when a pair of points are near to one another.</p>
 *
 * <p>The class contains several static instances of itself, namely,
 * EUCLID, MAX, and SUM.  The following relationships hold:</p>
 *
 * <pre>  MAX &lt;= EUCLID &lt;= SUM &lt;= 2 * MAX</pre>
 *
 * @author Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public abstract class Metric {

    /**
     * <p>Returns this distance between (x1,y1) and (x2,y2) in this
     * metric.</p>
     *
     * <p>This is the single abstract method that needs to be
     * instantiated.</p>
     *
     * @param x1 the x-coordinate of point 1
     * @param y1 the y-coordinate of point 1
     * @param x2 the x-coordinate of point 2
     * @param y2 the y-coordinate of point 2
     */
    public abstract double distance(double x1, double y1, double x2, double y2);
    
    
    /**
     * <p>Returns the distance between points p1 and p2.</p>
     *
     * <p>Returns <code>Double.MAX_VALUE</code> if either parameter
     * is <code>null</code>.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     */
    public final double distance(Point2D p1, Point2D p2) {
        if ((p1 == null) || (p2 == null))
            return Double.MAX_VALUE;
        
        return distance(p1.getX(), p1.getY(), p2.getX(), p2.getY());
    }
    
    
    /**
     * <p>Returns the distance between points p1 and p2.</p>
     *
     * <p>Returns <code>Double.MAX_VALUE</code> if either parameter
     * is <code>null</code> or not of length 2.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     */
    public final double distance(double[] p1, double[] p2) {
        if ((p1 == null) || (p2 == null))
            return Double.MAX_VALUE;
        
        if ((p1.length != 2) || (p2.length != 2))
            return Double.MAX_VALUE;
        
        return distance(p1[0], p1[1], p2[0], p2[1]);
    }
    
    
    /**
     * <p>Returns the distance between points p1 and p2.</p>
     *
     * <p>Returns <code>Double.MAX_VALUE</code> if either parameter
     * is <code>null</code> or not of length 2.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     */
    public final double distance(float[] p1, float[] p2) {
        if ((p1 == null) || (p2 == null))
            return Double.MAX_VALUE;
        
        if ((p1.length != 2) || (p2.length != 2))
            return Double.MAX_VALUE;
        
        return distance(p1[0], p1[1], p2[0], p2[1]);
    }
    
    
    /**
     * <p>Returns true if (x1,y1) and (x2,y2) have distance less
     * than or equal to epsilon.</p>
     *
     * @param x1 the x-coordinate of point 1
     * @param y1 the y-coordinate of point 1
     * @param x2 the x-coordinate of point 2
     * @param y2 the y-coordinate of point 2
     * @param epsilon the measure of nearness
     */
    public final boolean isNear
        (double x1, double y1, double x2, double y2, double epsilon)
    {
        return distance(x1, y1, x2, y2) <= epsilon;
    }
    
    
    /**
     * <p>Returns true if points p1 and p2 have distance less
     * than or equal to epsilon.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     * @param epsilon the measure of nearness
     */
    public final boolean isNear
        (Point2D p1, Point2D p2, double epsilon)
    {
        return distance(p1, p2) <= epsilon;
    }
    
    
    /**
     * <p>Returns true if points p1 and p2 have distance less
     * than or equal to epsilon.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     * @param epsilon the measure of nearness
     */
    public final boolean isNear
        (double[] p1, double[] p2, double epsilon)
    {
        return distance(p1, p2) <= epsilon;
    }
    
    
    /**
     * <p>Returns true if points p1 and p2 have distance less
     * than or equal to epsilon.</p>
     *
     * @param p1 point 1
     * @param p2 point 2
     * @param epsilon the measure of nearness
     */
    public final boolean isNear
        (float[] p1, float[] p2, double epsilon)
    {
        return distance(p1, p2) <= epsilon;
    }
    
    
    /**
     * <p>The EUCLID metric computes the distance between (x1, y1)
     * and (x2,y2) using the classic euclidean metric.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  Math.sqrt((x * x) + (y * y))</pre>
     *
     * <p>where:</p>
     *
     * <pre>  x = x2 - x1</pre>
     *
     * <pre>  y = y2 - y1</pre>
     *
     * <p>To reduce the chance of overflow, the computation is
     * actually done in a more mathematically stable fashion but
     * the answer is in effect the same as the above formula.</p>
     */
    public static final Metric EUCLID =
        new Metric() {
            public double distance
                (double x1, double y1, double x2, double y2)
            {
                double x = Math.abs(x2 - x1);
                double y = Math.abs(y2 - y1);
                
                if ((x == 0) && (y == 0))
                    return 0;
                
                if (x <= y) {
                    double z = x / y;
                    
                    return y * Math.sqrt(1 + (z * z));
                }
                else {
                    double z = y / x;
                    
                    return x * Math.sqrt(1 + (z * z));
                }
            }
        };
    
    
    /**
     * <p>The MAX metric computes the distance between (x1, y1)
     * and (x2,y2) using the maximum of the absolute values of
     * the differences in the coordinates.</p>
     */
    public static final Metric MAX =
        new Metric() {
            public double distance
                (double x1, double y1, double x2, double y2)
            {
                double x = Math.abs(x2 - x1);
                double y = Math.abs(y2 - y1);
                
                return Math.max(x, y);
            }
        };
    
    
    /**
     * <p>The SUM metric computes the distance between (x1, y1)
     * and (x2,y2) using the sum of the absolute values of the
     * differences in the coordinates.</p>
     */
    public static final Metric SUM =
        new Metric() {
            public double distance
                (double x1, double y1, double x2, double y2)
            {
                double x = Math.abs(x2 - x1);
                double y = Math.abs(y2 - y1);
                
                return x + y;
            }
        };
    
}

