/**
 * @author
 *  Behrooz Badii 	bb2122@columbia.edu
 *  Hanhua Feng         hanhua@cs.columbia.edu
 *  Edan Harel 	        edan@columbia.edu
 */
package CookieCutter.g6;

import java.io.Serializable;

/**
 * A line segment on the xy-plane
 */
public class Segment extends Line implements Serializable {
    Point _v0, _v1;

    /** given two points, create a line segment
     */
    public Segment( Point v0, Point v1 ) {
        super( v0, v1 );
        _v0 = v0;
        _v1 = v1;
    }

    public final double x0() {
        return _v0.x();
    }

    public final double y0() {
        return _v0.y();
    }

    public final double x1() {
        return _v1.x();
    }

    public final double y1() {
        return _v1.y();
    }

    public final Point p0() {
        return _v0;
    }

    public final Point p1() {
        return _v1;
    }

    /** partition a line segment into two parts.
     */
    public final Point partition( double r ) {
        double r1 = 1 - r;
        return new Point( _v0.x() * r + _v1.x() * r1,
                          _v0.y() * r + _v1.y() * r1 );
    }

    // whether value x is in span(a,b)
    private static final boolean _is_between( double x, double a, 
                                              double b, double eps ) {
        if ( a > b )
        {
            double t = a;
            a = b;
            b = t;
        }

        a -= eps;
        b += eps;

        return x >= a && x <= b;
    }

    // if two points are close, return one of them, otherwise create a line
    private static final Object _create_segment( Point p0, Point p1 ) {
        if ( p0.equals( p1 ) )
            return p0;
        return new Segment( p0, p1 );
    }

    /** get the intersection of two line segments -- either a point,
     * a line segment, or null indicating they do not intersect.
     */
    public Object intersection( Segment s ) {
        // System.out.println( toString() + " X " + s.toString() + "?" );

        // calculate the crosspoint of two infinite lines
        Object p = super.crosspoint( s );
        // if they are parallel, return null
        if ( null == p )
            return null;

        if ( p instanceof Line ) 
        {
            // two segments are on the same line.
            double a0, a1, b0, b1;
            // use either x- or y-coordinates, whichever is more
            // error-prone.
            if ( Math.abs( _a ) < 0.707 )
            {
                a0 = _v0.x();
                a1 = _v1.x();
                b0 = s._v0.x();
                b1 = s._v1.x();
            }
            else
            {
                a0 = _v0.y();
                a1 = _v1.y();
                b0 = s._v0.y();
                b1 = s._v1.y();
            }

            // check the intersection of two intervals
            if ( _is_between( b0, a0, a1, Plane.EPS ) )
            {
                if ( _is_between( b1, a0, a1, Plane.EPS ) )
                    return s;
                if ( _is_between( a1, b0, b1, Plane.EPS ) )
                    return _create_segment( _v1, s._v0 );
                return _create_segment( _v0, s._v0 );
            }
            
            if ( _is_between( b1, a0, a1, Plane.EPS ) )
            {
                if ( _is_between( a1, b0, b1, Plane.EPS ) )
                    return _create_segment( _v1, s._v1 );
                return _create_segment( _v0, s._v1 );
            }
            
            if ( _is_between( a0, b0, b1, Plane.EPS ) 
                 && _is_between( a1, b0, b1, Plane.EPS ) )
                return this;

            return null;
        }
        else
        {
            // must be a point
            double x = ((Point)p).x();
            double y = ((Point)p).y();

            // this point must be on both segments
            if ( _is_between( x, _v0.x(), _v1.x(), Plane.EPS ) 
                 && _is_between( y, _v0.y(), _v1.y(), Plane.EPS )
                 && _is_between( x, s._v0.x(), s._v1.x(), Plane.EPS )
                 && _is_between( y, s._v0.y(), s._v1.y(), Plane.EPS ) )
                return p;

            return null;
        }
    }

    /** calculate the minimum distance between this segment to point
     * p.
     */
    public final double distance( Point p ) {
        Point foot = foot(p);
        double a0, a1, af;
        if ( Math.abs( _a ) < 0.707 )
        {
            a0 = _v0.x();
            a1 = _v1.x();
            af = foot.x();
        }
        else
        {
            a0 = _v0.y();
            a1 = _v1.y();
            af = foot.y();
        }

        if ( a0 <= af && af <= a1 || a0 >= af && af >= a1 )
            return p.distance( foot );
        
        return Math.min( p.distance( _v0 ), p.distance( _v1 ) );

    }

    /** calculate the minimum distance between this segment to line
     * segment s.  
     */
    public final double distance( Segment s ) {
        throw new UnsupportedOperationException();
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append( _v0.toString() );
        sb.append( "-" );
        sb.append( _v1.toString() );

        return new String(sb);
    }
}
