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

import java.util.Vector;
import java.io.Serializable;

/**
 * A polygon on the plan
 */
public class Polygon implements Serializable {
    int _n;
    Point[] _vs;         // vertices
    Segment[] _es;       // edges: _es[i]: _vs[i]->_v[i+1]

    /** create a polygon by vertices
     */
    public Polygon( Point[] vs ) {
        _n = vs.length;
        _vs = vs;
        _es = new Segment[ _n ];

        int j = _n - 1;
        for ( int i=0; i<_n; j=i++ ) 
            _es[j] = new Segment( vs[j], vs[i] );
    }

    /** create a polygon from a rectangle
     */
    public Polygon( Rectangle r ) {
        this( new Point[] {
            new Point( r.x0(), r.y0() ),
            new Point( r.x1(), r.y0() ),
            new Point( r.x1(), r.y1() ),
            new Point( r.x0(), r.y1() ) } );
    }

    /** get the number of vertices
     */
    public final int n() {
        return _n;
    }

    /** the x-coordinate of the idx-th vertex
     */
    public final double x( int idx ) {
        return _vs[idx].x();
    }

    /** the y-coordinate of the idx-th vertex
     */
    public final double y( int idx ) {
        return _vs[idx].y();
    }
    
    /** the idx-th vertex
     */
    public final Point vertex( int idx ) {
        return _vs[idx];
    }

    /** the idx-th edge
     */
    public final Segment edge( int idx ) {
        return _es[idx];
    }

    /** the idx-th angle
     */
    public final double angle( int idx ) {
        double ret;
        if ( 0 == idx )
            ret = _es[_n-1].angle( _es[0] );
        else
            ret = _es[idx-1].angle( _es[idx] );

        if ( isNegative() )
            return Math.PI + ret;
        return Math.PI - ret;
    }

    /** the previous index
     */
    public final int previous( int i ) {
        if ( i > 0 )
            return i-1;
        return _n-1;
    }

    /** the next index
     */
    public final int next( int i ) {
        if ( i >= _n-1 )
            return 0;
        return i+1;
    }

    /** the closest vertex to a line
     */
    public final Point closest( Line ln ) {
        Point ret = _vs[0];
        double dist = ln.distance( _vs[0] );
        for ( int i=1; i<_n; i++ )
        {
            double d = ln.distance( _vs[i] );
            if ( d < dist )
            {
                dist = d;
                ret = _vs[i];
            }
        }
        return ret;
    }

    /** the polygon must have no less than 3 vertices
     */
    final boolean isValid() {
        return _n >= 3;
    }

    /** a polygon is defined to be simple if the edges do not
     * intersect each other. O(n^2)
     */
    final boolean isSimple() {
        for ( int j=2; j<_n-1; j++ )
            if ( null != _es[0].intersection( _es[j] ) )
                return false;

        for ( int i=1; i<_n-2; i++ ) 
            for ( int j=i+2; j<_n; j++ )
                if ( null != _es[i].intersection( _es[j] ) )
                    return false;

        return true;
    }

    /** check whether a polygon is convex
     */
    final boolean isConvex() {
        int s = _es[0].whichSide( _vs[_n-1] );

        for ( int i=1; i<_n; i++ )
        {
            int s1 = _es[i].whichSide( _vs[i-1] );

            if ( s + s1 == 0 )
                return false;
            s |= s1;
        }

        return true;
    }

    /** check whether a SIMPLE polygon is negative.  We define the
     * polygon is positive if the vertices are ordered
     * counter-clockwisely, and negative if clockwisely.
     */
    final boolean isNegative() {
        double dx = 0.0, dy = 0.0, d = 0.0;
        
        int i;
        for ( i=0; i<_n-1; i++ )
        {
            dx = _vs[1].x() - _vs[0].x();
            dy = _vs[1].y() - _vs[0].y();
            d = Math.sqrt( dx*dx + dy*dy );
            if ( d > Plane.EPS )
                break;
        } 

        if ( i == _n-1 )
            return false;

        d = 2.5 / d * Plane.EPS;
        d *= Math.max( 
            Math.max( Math.abs(_vs[0].x()), Math.abs(_vs[1].x()) ),
            Math.max( Math.abs(_vs[1].y()), Math.abs(_vs[0].y()) ) );
        dx *= d;
        dy *= d;

        Point p = new Point( 
            ( _vs[1].x() * 0.492 + _vs[0].x() * 0.508 ) + dy,
            ( _vs[1].y() * 0.492 + _vs[0].y() * 0.508 ) - dx );

        return contains( p );
    }

    /** get the rectangular bound
     */
    public final Rectangle bound() {
        double x0 = _vs[0].x();
        double y0 = _vs[0].y();
        double x1 = x0;
        double y1 = y0;

        for ( int j=1; j<_n; j++ )
        {
            double x = _vs[j].x();
            double y = _vs[j].y();
            if ( x < x0 )
                x0 = x;
            else if ( x > x1 )
                x1 = x;
            if ( y < y0 )
                y0 = y;
            else if ( y > y1 )
                y1 = y;
        }

        return new Rectangle( x0, y0, x1-x0, y1-y0 );
    }


    /** check whether a polygon intersects a line.
     */
    public final boolean intersects( Segment s ) {
        for ( int i=0; i<_n; i++ )
            if ( null != _es[i].intersection( s ) )
                return true;
        return false;
    }


    /** check whether two polygons intersect.  O(n^2)
     */
    public final boolean intersects( Polygon pg ) {
/*
        Point c0 = center();
        double r0 = radius( c0 );
        Point c1 = pg.center();
        double r1 = pg.radius( c1 );

        if ( c0.distance(c1) > r0 + r1 + Plane.EPS )
            return false;
*/
        for ( int i=0; i<_n; i++ )
            for ( int j=0; j<pg._n; j++ )
                if ( null != _es[i].intersection( pg._es[j] ) )
                     return true;
                     
        return false;
    }


    /** dilate (expand) a polygon by delta
     */
    public final Polygon dilate( double delta ) {
        double d = ( isNegative() ? delta : -delta );
        
        Vector ret = new Vector();
        Line ln0 = _es[_n-1].shift( d );
        for ( int i=0; i<_n; i++ ) 
        {
            Line ln1 = _es[i].shift( d );

            if ( Math.abs( ln0.angle(ln1) ) > Math.PI * (5.0/6.0) )
            {
                ret.add( ln0.translateAlong( ln0.foot( _vs[i] ), -delta ) );
                ret.add( ln1.translateAlong( ln1.foot( _vs[i] ), delta ) );
            }
            else
            {
                Object o = ln0.crosspoint( ln1 );
                if ( null == o || o instanceof Line )
                    ret.add( ln0.foot( _vs[i] ) );
                else
                    ret.add( o );
            }

            ln0 = ln1;
        }

        return new Polygon( (Point[]) ret.toArray( new Point[0] ) );
    }

    /** check whether a polygon contains a point.
     *
     * Algorithm of Wm Randolph Franklin.
     * http://www.ecse.rpi.edu/Homepages/wrf/research/geom/pnpoly.html
     */
    public final boolean contains( Point p ) {
        boolean ret = false;
        double x = p.x();
        double y = p.y();

        // Painter.main.draw( new Point(x,y), 0x00ff00 );

        for ( int i = 0, j = _n-1; i<_n; j = i++ )
        {
            if ( _vs[i].y() <= y && y < _vs[j].y() 
                 || _vs[j].y() <= y && y < _vs[i].y() )
            {
                double cx = ( _vs[j].x() - _vs[i].x() )
                    * ( y - _vs[i].y() ) / ( _vs[j].y() - _vs[i].y() )
                    + _vs[i].x();
/*
                if ( x < cx )
                    Painter.main.draw( new Point(cx,y), 0xff0000 );
*/
                if ( x < cx )
                    ret = !ret;
            }
        }
        
        return ret;
    }

    /** compute the minimum distance between a point p to a point
     */
    public final double distance( Point p ) {
        double d = _es[0].distance( p );
        for ( int i=1; i<_n; i++ )
        {
            double d1 = _es[i].distance( p );
            if ( d1 < d )
                d = d1;
        }

        if ( contains( p ) )
            return -d;
        return d;
    }

    public static final double DELTA = 1E-5;

    /** the upper bound of distance of the origins of two polygon 
     * when they intersect.
     */
    public final double contactDistance( Polygon pg ) {
        Rectangle r0 = bound();
        Rectangle r1 = pg.bound();
        double dmax = r0.width() + r1.width() + EPS;
        Point p = pg.contactPos( 
            new Polygon[]{ this },
            new Segment( new Point( dmax, 0 ), Point.ORIGIN ) );
        return p.x();
    }

    public final double contactDistance( Polygon[] pgs ) {
        Rectangle r0 = bound();
        Rectangle r1 = pgs[0].bound();

        for ( int i=1; i<pgs.length; i++ )
            r1 = r1.union( pgs[i].bound() );

        double dmax = r0.width() + r1.width() + EPS;

        Point p = contactPos( pgs,
            new Segment( new Point( dmax, 0 ), Point.ORIGIN ) );
        return p.x();
    }

    public final double contactDistanceObsolete( Polygon pg ) {
        Rectangle r0 = bound();
        Rectangle r1 = pg.bound();
        double dmax = r0.width() + r1.width() + EPS;
        double d = dmax * 0.5;
        double ret = dmax;

    outer_loop:
        for ( double dd=d*0.5; dd>=DELTA; dd *= 0.5 )
        {
            Polygon pgt = pg.translate( d, 0 );
            if ( intersects( pgt ) ) 
            {
                d += dd;
                continue;
            }
            
            for ( int i=0; i<pg.n(); i++ )
            {
                Point p = pgt.vertex(i);
                Segment s = new Segment( p, new Point( pg.x(i)+d, pg.y(i) ) );
                if ( intersects( s ) )
                {
                    d += dd;
                    continue outer_loop;
                }
            }

            ret = d;
            d -= dd;
        }
        
        return ret;
    }

    private static final double _dist_between( Object o, Point p ) {
        if ( o instanceof Segment )
        {
            Segment s = (Segment) o;
            return Math.min( p.distance( s.p0() ), p.distance( s.p1() ) );
        }
    
        return p.distance( (Point) o );
    }

    /** move the origin of this polygon along the segment, until
     * the polygon touchs one of pgs.  Return the point.
     */
    public final Point contactPos( Polygon[] pgs, Segment seg ) {
        double a = seg.a();
        double b = seg.b();
        double d = seg.p0().distance( seg.p1() );
        Point p0 = new Point(0,0), p1 = new Point(0,0);
        double x0 = seg.x0(), x1 = seg.x1(), y0 = seg.y0(), y1 = seg.y1();

        for ( int i=0; i<pgs.length; i++ )
        {
            Polygon pg = pgs[i];

            for ( int j=0; j<pg._n; j++ )
            {
                p0.setX( pg.x(j) - x0 );
                p0.setY( pg.y(j) - y0 );
                p1.setX( pg.x(j) - x1 );
                p1.setY( pg.y(j) - y1 );

                Segment s0 = new Segment( p0, p1 );

                for ( int k=0; k<_n; k++ )
                {
                    Object o = _es[k].intersection( s0 );

                    if ( o == null )
                        continue;
                    double dist = _dist_between( o, p0 );

                    if ( dist < d )
                        d = dist;
                }
            }

            for ( int j=0; j<_n; j++ )
            {
                p0.setX( x(j) + x0 );
                p0.setY( y(j) + y0 );
                p1.setX( x(j) + x1 );
                p1.setY( y(j) + y1 );

                Segment s0 = new Segment( p0, p1 );

                for ( int k=0; k<pg._n; k++ )
                {
                    Object o = pg._es[k].intersection( s0 );
                    if ( o == null )
                        continue;
                    double dist = _dist_between( o, p0 );
                    if ( dist < d )
                        d = dist;
                }
            }
        }

        return new Point( x0 + b*d, y0 - a*d );
    }

    public final Point contactPosObsolete( Polygon[] pgs, Segment seg ) {
        double d = 0.5;
        double ret = 1.0;

    outer_loop:
        for ( double dd=d*0.5; dd>=DELTA; dd *= 0.5 )
        {
            Point p = seg.partition( d );
            Polygon pgt = translate( p.x(), p.y() );

            for ( int j=0; j<pgs.length; j++ )
                if ( pgt.intersects( pgs[j] ) ) 
                {
                    d -= dd;
                    continue outer_loop;
                }
            
            ret = d;
            d += dd;
        }
        
        return seg.partition( ret );
    }

    // add all negative distances together
    private double _sum_neg_dist_by_offset( Polygon pg, 
                                            double dx, double dy ) {
        double ret = 0;
        for ( int j=0; j<pg.n(); j++ )
        {
            double dist = distance( new Point( pg.x(j)+dx, pg.y(j)+dy ) );
            if ( dist < 0 )
                ret += dist;
        }

        return ret;
    }

    /** try to find smaller distance than standard (puzzle like)
     */
    public final double minDistance( Polygon pg, double maxdist ) {
        double dd = maxdist / 16.0;
        double dist = -1E100;
        int idx = 0;

        for ( int i=0; i<16; i++ )
        {
            double d = dd * i;
            double sum = _sum_neg_dist_by_offset( pg, d, 0 );
            if ( sum > 0 )
                return d;
            if ( sum > dist )
            {
                idx = i;
                dist = sum;
            }
        }

        double d = dd * idx;
        dd *= 0.5;

        for ( ; d >= DELTA; dd *= 0.5 )
        {
            double dist1 = _sum_neg_dist_by_offset( pg, d-dd, 0 );
            double dist2 = _sum_neg_dist_by_offset( pg, d+dd, 0 );
            if ( dist1 > 0 )
                return d-dd;
            if ( dist2 > 0 )
                return d+dd;

            if ( dist1 > dist2 )
            {
                if ( dist > dist2 )
                {
                    d += dd;
                    dist = dist2;
                }
            }
            else if ( dist > dist1 )
            {
                d -= dd;
                dist = dist1;
            }                
        }

        return maxdist;
    }

    /** coordinate transform with certain transform matrix 
     */
    public final Polygon transform( double[] mat ) {
        Point[] ps = new Point[ _n ];
        for ( int i=0; i<_n; i++ )
            ps[i] = _vs[i].transform( mat );
        return new Polygon( ps );
    }

    public final Polygon place( double xt, double yt, double alpha ) {
        double[] mat = Plane.transmatrix( -xt, -yt, -alpha );
        return transform( mat );
    }

    public final Polygon translate( double xt, double yt ) {
        return place( xt, yt, 0 );
    }

    public final Polygon rotate( double alpha ) {
        return place( 0, 0, alpha );
    }

    public final Polygon rotate( double x0, double y0, double alpha ) {
        double[] mat = Plane.rotatematrix( x0, y0, alpha );
        return transform( mat );
    }

    // let a point at v0 move toward v1 for a distance no more than d
    public static Point _move_toward( Point v0, Point v1, double d ) {
        double x = v1.x() - v0.x();
        double y = v1.y() - v0.y();
        double r = Math.sqrt( x*x + y*y );
        if ( d >= r )
            return v1;
        if ( d <= 1E-11 )
            return v0;

        return new Point( v0.x() + x*d/r, v0.y() + y*d/r );
    }

    /** get the centroid of a polygon, assuming that the mass of this
     * polygon are equally distributed on all vertices.
     */
    public Point centroid() {
        double x=0, y=0;
        for ( int i=0; i<_n; i++ )
        {
            x += _vs[i].x();
            y += _vs[i].y();
        }

        return new Point( x/_n, y/_n );
    }

    /** Compute the center of the minimum circle that covers this polygon.
     * Is this an NP-hard problem?
     */
    public Point center() {
        // starting from the centroid
        Point ret = centroid();

        // if the polygon has no more than 2 vertices, the centroid
        // is actually the center
        if ( _n <= 2 )
            return ret;

        // try only for 8 interations
        for ( int j=0; j<8; j++ ) 
        {
            Point v0 = null, v1 = null, v2 = null;
            double d0 = -1.0, d1 = -1.0, d2 = -1.0;

            // find three furthest vertices, store them to v0, v1, and
            // v2, in this order, and store the distances to d0, d1,
            // and d2, in this order
            for ( int i=0; i<_n; i++ ) 
            {
                double d = ret.distance( _vs[i] );

                if ( d > d0 ) 
                {
                    d2 = d1;
                    d1 = d0;
                    d0 = d;
                    v2 = v1;
                    v1 = v0;
                    v0 = _vs[i];
                } 
                else if ( d > d1 ) 
                {
                    d2 = d1;
                    d1 = d;
                    v2 = v1;
                    v1 = _vs[i];
                } 
                else if ( d > d2 ) 
                {
                    d2 = d;
                    v2 = _vs[i];
                }
            }

            // If we get the same distance to all three vertices, we
            // are done.  Because we started from the centroid,
            // it should be outside of this triangle
            if ( d0 - d2 < 1E-10 )
                return ret;

            if ( d0 - d1 > d1 - d2 )
                ret = _move_toward( ret, v0, (d0-d1)*0.5 );
            else
            {
                Point dir = new Point(
                    ( v0.x() + v1.x() ) / 2.0,
                    ( v0.y() + v1.y() ) / 2.0 );

                if ( ret.distance( dir ) < 1E-10 )
                    return ret;

                ret = _move_toward( ret, dir, (d0-d2)*0.5 );
            }
        }

        return ret;
    }    

    /** Compute the distance from the center to the furthest vertex.
     */
    public double radius( Point center ) {
        double ret = 0;
        for ( int i=0; i<_n; i++ ) 
        {
            double d = center.distance( _vs[i] );
            if ( ret < d )
                ret = d;
        }

        return ret;
    }

    /** change the sign of the polygon -- which means reverse the
     * vertex order
     */
    public Polygon negate() {
        Point[] vs = new Point[ _n ];
        vs[0] = _vs[0];
        for ( int i=1; i<_n; i++ )
            vs[i] = _vs[_n-i];

        return new Polygon( vs );
    }

    /** get a positive representation of this polygon
     */
    public Polygon abs() {
        if ( isNegative() )
            return negate();
        return this;
    }


    /** get the convex hull.  O(n^2)
     */
    public final Polygon convexHull() {
        throw new UnsupportedOperationException();
    }


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

        sb.append( "[" );
        sb.append( _n );
        for ( int i=0; i<_n; i++ )
        {
            sb.append( " " );
            sb.append( _vs[i].toString() );
        }
        sb.append( "]" );

        return new String( sb );
    }

    //
    // The following code are experimental
    //

    static final double EPS = Plane.EPS * 5.0;

    /**
     * add all crosspoints of two polygons to them as vertices
     */
    public Polygon[] crossify( Polygon pg ) {
        // create new polygons stored in Vectors
        Vector npg0 = new Vector();
        Vector npg1 = new Vector();
        boolean changed = false;

        for ( int i=0; i<_n; i++ )
            npg0.add( _vs[i] );
        for ( int i=0; i<pg._n; i++ )
            npg1.add( pg._vs[i] );

        // for each edge of the first polygon.  Note npg0.size() is
        // increasing
        for ( int i=0; i<npg0.size(); i++ )
        {
            int pi = ( (i>0)? i : npg0.size() ) - 1;
/*
            System.out.println( "Current edge of this: " + pi + "->"
                                + i + " of " + npg0.size() );
*/
            Point s0 = (Point) npg0.elementAt( pi );
            Point t0 = (Point) npg0.elementAt( i );
            Segment st0 = new Segment( s0, t0 );   // the edge
            boolean changed0 = false;

            // for each edge of the second polygon.
            for ( int j=0; j<npg1.size(); j++ )
            {
                int pj = ( (j>0)? j : npg1.size() ) - 1;
/*
                System.out.println( "Current edge of pg: " + pj + "->"
                                    + j + " of " + npg1.size() );
*/
                Point s1 = (Point) npg1.elementAt( pj );
                Point t1 = (Point) npg1.elementAt( j );
                Segment st1 = new Segment( s1, t1 );
                boolean changed1 = false;
                
                // compute the intersection of two edges
                Object o = st0.intersection( st1 );
                // do nothing is there is no intersection
                if ( null == o )
                    continue;
/*
                System.out.println( "CROSS: " + st0.toString() 
                                    + " vs " + st1.toString()
                                    + " => " + o.toString() );
*/
                // they intersect.  Polygon is going to be changed
                changed = true;

                if ( o instanceof Segment )
                {
                    // do nothing if two edges are exactly identical
                    if ( ( s0 == s1 && t0 == t1 ) || ( s0 == t1 && t1 == t0 ) )
                        continue;

                    // System.out.println( " ******* line Crossing ****** " );

                    // if the intersection is a line segment
                    Segment seg = (Segment) o;
                    
                    if ( seg.p0().distance( seg.p1() ) <= EPS )
                    {
                        // handle a short segment as a point
                        o = seg.p0().midpoint( seg.p1() );
                    }
                    else
                    {
                        // the segment is long
                        Point p0 = seg.p0();
                        Point p1 = seg.p1();

                        // check whether s0 is close to the intersection
                        if ( s0.distance( p0 ) <= EPS )
                            p0 = s0;
                        else if ( s0.distance( p1 ) <= EPS )
                            p1 = s0;
                        else
                            changed0 = true;
                        
                        // check whether t0 is close to the intersection
                        if ( t0.distance( p0 ) <= EPS )
                            p0 = t0;
                        else if ( t0.distance( p1 ) <= EPS )
                            p1 = t0;
                        else
                            changed0 = true;
                        
                        // check wheter s1 is close to the intersection
                        if ( s1.distance( p0 ) <= EPS )
                        {
                            if ( p0 == s0 || p0 == t0 )
                            {
                                s1 = p0;
                                npg1.setElementAt( p0, pj );
                            }
                            else
                                p0 = s1;
                        }
                        else if ( s1.distance( p1 ) <= EPS )
                        {
                            if ( p1 == s0 || p1 == t0 )
                            {
                                s1 = p1;
                                npg1.setElementAt( p1, pj );
                            }
                            else
                                p1 = s1;
                        }
                        else
                            changed1 = true;

                        // check wheter t1 is close to the intersection
                        if ( t1.distance( p0 ) <= EPS )
                        {
                            if ( p0 == s0 || p0 == t0 )
                            {
                                t1 = p0;
                                npg1.setElementAt( p0, j );
                            }
                            else
                                p0 = t1;
                        }
                        else if ( t1.distance( p1 ) <= EPS )
                        {
                            if ( p1 == s0 || p1 == t0 )
                            {
                                t1 = p1;
                                npg1.setElementAt( p1, j );
                            }
                            else
                                p1 = t1;
                        }
                        else                                
                            changed1 = true;

                        // is the second edge changed?
                        if ( changed1 )
                        {
                            if ( s1.distance( p0 ) > s1.distance( p1 ) )
                            {
                                Point tmp = p0;
                                p0 = p1;
                                p1 = tmp;
                            }

                            if ( p1 != t1 )
                                npg1.insertElementAt( p1, j );
                            if ( p0 != s1 )
                                npg1.insertElementAt( p0, j );
                        }
                        
                        // is the first edge changed?
                        if ( changed0 )
                        {                        
                            if ( s0.distance( p0 ) > s0.distance( p1 ) )
                            {
                                Point tmp = p0;
                                p0 = p1;
                                p1 = tmp;
                            }
                            
                            if ( p1 != t0 )
                                npg0.insertElementAt( p1, i );
                            if ( p0 != s0 )
                                npg0.insertElementAt( p0, i );
                        }

                    }
                }

                if ( o instanceof Point )
                {
                    if ( s0 == s1 || t0 == t1 || s0 == t1 || s1 == t0 )
                        continue;

                    // the intersection of two edges is a point
                    Point p = (Point) o;

                    // is this crosspoint close to an end point of the
                    // first edge?  If so, we move the crosspoint to
                    // the end point.
                    if ( p.distance( s0 ) <= EPS )
                        p = s0;
                    else if ( p.distance( t0 ) <= EPS )
                        p = t0;
                    else
                        changed0 = true;

                    // is this crosspoint close to and end point of
                    // the second edge?
                    if ( p.distance( s1 ) <= EPS )
                    {
                        // if so, check whether this point has been
                        // moved to one of the end point of the first
                        // edge.
                        if ( changed0 )
                        {
                            // no, we move the crosspoint to the end
                            // point of the second edge
                            p = s1;
                        }
                        else
                        {
                            // yes, we move the endpoint of the second
                            // edge to the crosspoint.
                            changed1 = true;
                            npg1.setElementAt( p, pj );
                        }
                    }
                    else if ( p.distance( t1 ) <= EPS )
                    {
                        // similar to the previous case
                        if ( changed0 )
                        {
                            p = t1;
                        }
                        else
                        {
                            changed1 = true;
                            npg1.setElementAt( p, j );
                        }
                    }
                    else
                    {
                        // if the crosspoint is not close to any end
                        // points, add the crosspoint BETWEEN two
                        // vertices of the second edge
                        changed1 = true;
                        npg1.insertElementAt( p, j );
                    }
                    
                    // so for the first edge.
                    if ( changed0 )
                        npg0.insertElementAt( p, i );
                }

                // if the first edge is changed, restart the outer loop
                if ( changed0 )
                    break;
                // otherwise, if the second edge is change, restart
                // the inner loop
                if ( changed1 )
                    j--;
            }

            // restart the outer loop?
            if ( changed0 )
                i--;
        }
        
        if ( changed )
        {
            return new Polygon[] {
                new Polygon( (Point[]) npg0.toArray( new Point[0] ) ),
                    new Polygon( (Point[]) npg1.toArray( new Point[0] ) ) };
        }

        return null;
    }

    static final int INSIDE = -1;
    static final int OUTSIDE = -2;

    /** verify the result of crossify.  Return an array of two array
     * elements: the first array is for this polygon, and the second
     * for pg.  Each element in arrays represent the location of a
     * vertex.  A positive number indicate the vertex is also in the
     * other polygon: the number is its index.  Value of -1 means this
     * vertex is outside of the other polygon and value of -2 means this
     * vertex is inside of the other polygon.
     */
    public int[][] vericrossify( Polygon pg ) throws GeometryException {
        // allocate space first
        int[][] ret = new int[2][];
        ret[0] = new int[_n];
        ret[1] = new int[pg._n];
        // first initial value to an illegal value
        for ( int i=0; i<_n; i++ )
            ret[0][i] = -3;
        for ( int j=0; j<pg._n; j++ )
            ret[1][j] = -3;

        int shared = -1;
        // then set up all shared vertices
        for ( int j=0; j<pg._n; j++ )
        {
            for ( int i=0; i<_n; i++ )
                if ( _vs[i] == pg._vs[j] )
                {
                    // sharing must be pairwise
                    if ( ret[0][i] >= 0 || ret[1][j] >= 0 )
                    {
                        String s = null;
                        if ( ret[0][i] >= 0 )
                        {
                            s = "this.vertex(" + i + ") is shared by both "
                                + "pg.vertex(" + ret[0][i] 
                                + ") and pg.vertex(" + j + ")";
                        }
                        
                        if ( ret[1][j] >= 0 )
                        {
                            s = "pg.vertex(" + j + ") is shared by both "
                                + "this.vertex(" + ret[1][j]
                                + ") and this.vertex(" + i + ")";
                        }
                        throw new GeometryException( s );
                    }
                    ret[0][i] = j;
                    ret[1][j] = i;
                    shared = i;
                }
        }
        
        // no shared point
        if ( shared < 0 )
        {
            boolean b1 = contains( pg._vs[0] );
            for ( int i=0; i<pg._n; i++ )
            {
                if ( contains( pg._vs[i] ) != b1 )
                    throw new GeometryException();
                // if this contains pg, pg <= this
                ret[1][i] = ( b1 ? INSIDE : OUTSIDE );
            }

            boolean b2 = pg.contains( _vs[0] );
            // polygons cannot contain each other
            if ( b2 && b1 )
                throw new GeometryException();
            for ( int i=1; i<_n; i++ )
            {
                if ( pg.contains( _vs[i] ) != b2 )
                    throw new GeometryException();
                // if pg congtains this, this <= bg
                ret[0][i] = ( b2 ? INSIDE : OUTSIDE );
            }

            return ret;
        }

        // now we have a shared point.

        // the in/out flag is not set
        boolean preset = false;
        boolean prevalue = false;
        for ( int i = next(shared); i != shared; i = next(i) ) 
        {
            if ( ret[0][i] >= 0 )
            {
                preset = false;
                continue;
            }

            boolean b = pg.contains(_vs[i]);
            if ( preset && b != prevalue )
                throw new GeometryException();
            preset = true;
            prevalue = b;
            ret[0][i] = ( b ? INSIDE : OUTSIDE );
        }

        preset = prevalue = false;
        shared = ret[0][shared];
        for ( int i = pg.next(shared); i != shared; i = pg.next(i) ) 
        {
            if ( ret[1][i] >= 0 )
            {
                preset = false;
                continue;
            }

            boolean b = contains(pg._vs[i]);
            if ( preset && b != prevalue )
                throw new GeometryException();
            preset = true;
            prevalue = b;
            ret[1][i] = ( b ? INSIDE : OUTSIDE );
        }

        return ret;
    }

    public static final int CMP_EQUAL = 0;
    public static final int CMP_SUPER = 1;
    public static final int CMP_SUB   = 2;
    public static final int CMP_DISJOINT = 3;
    public static final int CMP_JOINT = 4;

    /** compare two polygons that do not have non vertex crosspoints
     * return 0 if two polygons are equivalent, 1 if this polygon is
     * larger, -1 if this polygon is smaller or -2 if they have disjoint
     * interiors.
     */
    public int compare( Polygon pg ) {
        int in0 = 0;
        int out0 = 0;
        int in1 = 0;
        int out1 = 0;

    outer_loop0:  // check all vertices of this polygon
        for ( int i=0; i<_n; i++ )
        {
            Point p = _vs[i];
            for ( int j=0; j<pg._n; j++ )
                if ( p == pg._vs[j] )
                    continue outer_loop0;

            if ( pg.contains( p ) )
                in0++;
            else
                out0++;
        }

        // if all vertices of this polygon are in pg, then this <= pg
        if ( 0 == out0 )
        {
            if ( 0 == in0 )
                return CMP_EQUAL;
            return CMP_SUB;
        }

    outer_loop1:  // check all of vertices of pg1
        for ( int i=0; i<pg._n; i++ )
        {
            Point p = pg._vs[i];
            for ( int j=0; j<_n; j++ )
                if ( p == _vs[j] )
                    continue outer_loop1;

            if ( contains( p ) )
                in1++;
            else
                out1++;
        }

        // if all vertices of pg are in this polygon, then this >= pg
        if ( 0 == out1 )
            return CMP_SUPER;
        // if no vertex of one polygon is in the other, they may
        // disjoint.  This can go wrong, only for reference.  A
        // counter-example is that a square and a rotated square
        // intersect each other, but this program returns disjoint.
        if ( 0 == in0 && 0 == in1 )
            return CMP_DISJOINT;    
        // otherwise, they intersect.
        return CMP_JOINT;
    }

    // starting at index 'start', find the first unused vertex
    int _find_first_unused( boolean[] used, int start ) {
        int ret = start;
        for ( int j=0; j<_n; j++ )
        {
            if ( !used[ret] )
                return ret;
            ret++;
            if ( ret >= _n )
                ret -= _n;
        } 

        return -1;
    }

    static boolean _between( int x, int r0, int r1 ) {
        return x <= r1 && ( r0 <= x || r0 > r1 )
            || x > r1 && r0 > r1 && r0 <= x;
    }

    int _arc_to( Polygon[] pgs, int[][] shared, int i, boolean forward ) {
        int n = ( forward ? pgs[1].next(i) : pgs[1].previous(i) );
        int m = shared[1][n];
        if ( m == OUTSIDE )
            return -1;
        if ( m >= 0 && !pgs[0].contains( 
                 pgs[1].vertex(i).midpoint( pgs[1].vertex(n) ) ) )
            return -1;
        while ( m < 0 )
        {
            n = ( forward ? pgs[1].next(n) : pgs[1].previous(n) );
            m = shared[1][n];
            // System.out.println( " =>pg."+n );
        }
        return m;
    }

    // cut pg0 into small polygons by pg1.  It returns two arrays of
    // polygons, one for those enclosed by pg1, the other for the rests.
    public Polygon[][] cutsets( Polygon pg ) throws GeometryException {

        // adding cross points into the polygon
        Polygon[] pgs = crossify( pg );
        if ( pgs == null )
            return null;

        System.out.println( "Find cut sets of " + pgs[0].toString() 
                            + " and " + pgs[1].toString() );

        int[][] shared = pgs[0].vericrossify( pgs[1] );

        for ( int i=0; i<shared[0].length; i++ )
            System.out.print( " ["+i+"]" + shared[0][i] );
        System.out.println();
        for ( int i=0; i<shared[1].length; i++ )
            System.out.print( " ["+i+"]" + shared[1][i] );
        System.out.println();


        int n = pgs[0]._n;
        // is this vertex used?
        boolean[] used = new boolean[n];
        // two sets of polygons, one for difference, one for intersection
        Vector[] plist = new Vector[]{ new Vector(), new Vector() };
        
        // repeat until all polygons are found
        for ( int first=0;; )
        {
            // find the first available vertices.
            first = pgs[0]._find_first_unused( used, first );
            if ( first < 0 )
                break;
            // the vector to store the vertices of the current polygon
            Vector vlist = new Vector();
            // yet not know which set the polygon belongs to
            int direction = -1;
            if ( shared[0][first] < 0 )
                direction = ( shared[0][first]==INSIDE ? 0 : 1 );

            // System.out.println( "Starting at vertex #: " + first );

            // repeat for all vertices in the current polygon
            for ( int i=first;; )
            {
                // add this vertex to the polygon
                vlist.add( pgs[0]._vs[i] );
                // System.out.println( " Adding " + i );

                // assert !used[i];
                if ( used[i] )
                {
                    // System.out.println( "Vertex " + i + " is used!" );
                    throw new GeometryException();
                }
                used[i] = true;

                // find the next vertex of pg0
                i = pgs[0].next(i);
                // if the start point is met, end this polygon
                if ( i == first )
                    break;

                int j = shared[0][i];
/*
                System.out.println( "Vertex #" + i + " is " +
                    (j>=0?"shared with pg." + j:"not shared") );
*/
                if ( j < 0 )
                {
                    int dir = ( j==INSIDE ? 0 : 1 );
                    if ( direction == 1-dir )
                        throw new GeometryException();
                    direction = dir;
                    continue;
                }

                // is the vertex shared?
                while ( j >= 0 )
                {
                    // this vertex is shared, check all edges
                    // incidents to the vertex
                    int fdst = _arc_to( pgs, shared, j, true );
                    int bdst = _arc_to( pgs, shared, j, false );

                    // System.out.println( "fdst=" + fdst + " bdst=" + bdst );
                    if ( !_between( fdst, i, first ) )
                        fdst = -1;
                    if ( !_between( bdst, i, first ) )
                        bdst = -1;

                    if ( fdst >= 0 && bdst >= 0 )
                    {
                        if ( _between( fdst, bdst, first ) )
                            bdst = -1;
                        else
                            fdst = -1;
                    }

                    if ( bdst >= 0 )
                    {
                        vlist.add( pgs[0]._vs[i] );
                        // System.out.println( " Adding " + i + " (reusable)" );

                        // traverse backward along pg1 util another
                        // shared vertex is found
                        for (;;)
                        {
                            j = pgs[1].previous(j);
                            if ( shared[1][j] >= 0 )
                                break;
                            // add vertices traversed to the polygon
                            vlist.add( pgs[1]._vs[j] );
                            // System.out.println( "   Adding pg." + j );
                        }

                        i = shared[1][j];
                    }
                    else if ( fdst >= 0 )
                    {
                        vlist.add( pgs[0]._vs[i] );
                        // System.out.println( " Adding " + i + " (reusable)" );

                        // until another shared vertex is found
                        for (;;)
                        {
                            j = pgs[1].next(j);
                            if ( shared[1][j] >= 0 )
                                break;
                            vlist.add( pgs[1]._vs[j] );
                            // System.out.println( "   Adding pg." + j );
                        }

                        i = shared[1][j];
                    }
                    else
                        break; // break the while, travel along pgs[0]
                }

                if ( i == first )
                    break;
            }

            if ( vlist.size() < 3 )
                continue;

            Polygon result = new Polygon( 
                (Point[])vlist.toArray( new Point[0] ) );

            // crossing type cannot be determined
            if ( direction < 0 )
            {
                // System.out.println( "Direction unknown, checking... " );
                int cmp = result.compare( pgs[1] );

                if ( cmp == CMP_DISJOINT )
                    direction = 1;
                else if ( cmp == CMP_EQUAL || cmp == CMP_SUB )
                    direction = 0;
                else if ( cmp == CMP_SUPER )
                    return null;
                else
                    throw new GeometryException();
                          
            }

            // a polygon is finished
            plist[direction].add( result );
/*
            System.out.println( "*** Add an polygon with " + 
                                result.n() + " vertices" );
*/
        }

        // all polygons are finished
        return new Polygon[][] {
            (Polygon[]) plist[0].toArray( new Polygon[0] ),
                (Polygon[]) plist[1].toArray( new Polygon[0] ) };
    }
}
