/**
 * @author
 *  Adam Rosenzweig - amr152
 *  Valerie Davidkova - vkd4
 *  Miqdad Mohammed - mm1723
 */

package CookieCutter.g0;
import java.util.Vector;
import java.io.Serializable;
import java.text.NumberFormat;


public class Connect implements Serializable {
    public static Connect X_AXIS = new Connect( 0, 1, 0 );
    public static Connect Y_AXIS = new Connect( 1, 0, 0 );

    protected double _a, _b, _c;   // The line of ax+by=c such that a*a+b*b=1

    /** create a line by the equation ax+by=c
     */
    public Connect( double a, double b, double c ) {
        double d = Math.sqrt( a*a + b*b );
        if ( d <= Plane.EPS )
        {
            _a = 0;
            _b = 1;
            _c = c;
        }
        else
        {
            d = 1.0 / d;

            _a = a * d;
            _b = b * d;
            _c = c * d;
        }
    }

    /** given two points, create a line.
     *
     * Note: we keep let the sign of b be the same as x_1-x_0, so that
     * the left-side points of the vector (p0->p1) always satisfy
     * ax+by>c.  It also works for x_1-x_0 = 0.
     */
    public Connect( Location p0, Location p1 ) {
        this( p0.y() - p1.y(),
              p1.x() - p0.x(),
              p0.y() * p1.x() - p1.y() * p0.x() );


    }

    public final double a() {
        return _a;
    }

    public final double b() {
        return _b;
    }

    public final double c() {
        return _c;
    }

    /** the distance to a Location
     */
    public double distance( Location p ) {
        return Math.abs( _a * p.x() + _b * p.y() - _c );
    }

    /** shift a line to the left-hand side by delta
     */
    public Connect shift( double delta ) {
        return new Connect( _a, _b, _c + delta );
    }

    /** get the foot of p to this line.
     */
    public Location foot( Location p ) {
        if ( Math.abs( _a*p.x() + _b*p.y() - _c ) <= Plane.EPS )
            return p;

        return (Location) crosspoint( new Connect( _b, -_a, _b*p.x() - _a*p.y() ) );
    }

    /** return the cross point of this line and line ln.
     *  It returns null if the two lines is Parellel, and a line
     *  if two lines are the same.
     */
    public final Object crosspoint( Connect ln ) {
        double det = _a * ln._b - _b * ln._a;

        if ( Math.abs(det) <= Plane.EPS * 10 )
        {
            if ( Math.abs( _c - ln._c ) > Plane.EPS
                 || _a * ln._a + _b * ln._b <= 0 )
                return null;

            return this;
        }

        det = 1.0 / det;
        return new Location( ( ln._b * _c - _b * ln._c ) * det,
                          ( _a * ln._c - ln._a * _c ) * det );
    }

    /** the angle of two lines.  Note that lines are directed,
     * @return the angle from this line to line ln, in a range of
     * (-\pi,\pi)
     */
    double angle( Connect ln ) {
        double a = Math.atan2( _a, _b ) - Math.atan2( ln._a, ln._b );
        if ( a > Math.PI )
            return a - Math.PI * 2;
        if ( a <= -Math.PI )
            return a + Math.PI * 2;
        return a;
    }

    public int whichSide( Location p ) {
        return Plane.sign( _a * p.x() + _b * p.y() - _c );
    }

    /** move the point p along the direction of this line by d
     */
    public Location translateAlong( Location p, double d ) {
        return new Location( p.x()-_b*d, p.y()+_a*d );
    }

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

        sb.append( "{(" );
        sb.append( Plane.format(_a) );
        sb.append( ")x+(" );
        sb.append( Plane.format(_b) );
        sb.append( ")y=" );
        sb.append( Plane.format(_c) );
        sb.append( "}" );

        return new String(sb);
    }
}
  /**
   *  A point on the xy-plane.
   */
   class Location implements Serializable {
      static final Location ORIGIN = new Location( 0, 0 );

      private double _x;          // the x-coordinate
      private double _y;          // the y-coordinate

      /** construct a point with x- and y-coordinates
       */
      public Location( double x, double y ) {
          _x = x;
          _y = y;
      }

      /** get the x coorindate
       */
      public final double x() {
          return _x;
      }

      /** get the y coordinate
       */
      public final double y() {
          return _y;
      }

      /** set the x coorindate: make sure the object is not shared
       */
      final void setX( double x ) {
          _x = x;
      }

      /** set the y coordinate: make sure the object is not shared
       */
      final void setY( double y ) {
          _y = y;
      }

      /** test whether the distance of two points are within the epsilon
       * value.
       */
      public final boolean equals( Location p ) {
          return distance( p) <= Plane.EPS;
      }

      /** calculate the distance between this point to the origin.
       */
      public final double distance() {
          return Math.sqrt( _x*_x + _y*_y );
      }

      /** calculate the distance between this point to point p.
       */
      public final double distance( Location p ) {
          return distance( p.x(), p.y() );
      }

      /** a shortcut of distance computation
       */
      public static final double distance( double x0, double y0,
                                           double x1, double y1 ) {
          return Math.sqrt( (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1) );
      }

      /** for speed-up
       */
      public static final double sqdistance( double x0, double y0,
                                             double x1, double y1 ) {
          return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1);
      }

      /** calculate the distance between this point to Location (x,y).
       */
      public final double distance( double x, double y ) {
          return Math.sqrt( (x-_x)*(x-_x) + (y-_y)*(y-_y) );
      }

      /** do a coordinate transform according to the transform matrix
       */
      public Location transform( double[] mat ) {
          return new Location( _x * mat[0] + _y * mat[1] + mat[2],
                            _x * mat[3] + _y * mat[4] + mat[5] );
      }

      /** translate a point by an offset of (xt,yt).  Equivalently,
       * transform to a parallel coordinates whose origin is at
       * (-xt,-yt).
       */
      public final Location translate( double xt, double yt ) {
          return transform( new double[]{ 1.0, 0.0, xt, 0.0, 1.0, yt } );
      }

      /** rotate a point counter-clockwisely at the origin by an angle
       * of alpha.  Equivalently, transform to a new coordinate systems
       * rotated whose x-axis has an angle of -alpha wrt the old system.
       */
      public final Location rotate( double alpha ) {
          return transform( Plane.transmatrix( 0, 0, -alpha ) );
      }

      /** rotate a point counter-clockwisely at (x0,y0) by an angle of alpha.
       * Equivalently, transform to a new coordinate systems rotated
       * whose x-axis has an angle of -alpha wrt the old system.
       */
      public final Location rotate( double x0, double y0, double alpha ) {
          return transform( Plane.rotatematrix( x0, y0, -alpha ) );
      }

      /** rotate a point counter-clockwisely by alpha, then translate
       * by offset of (xt,yt)
       */
      public final Location place( double xt, double yt, double alpha ) {
          return transform( Plane.transmatrix( -xt, -yt, -alpha ) );
      }

      /** get the midpoint of two points
       */
      public final Location midpoint( Location p ) {
          return new Location( ( _x + p._x ) * 0.5, ( _y + p._y ) * 0.5 );
      }

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

          sb.append( "(" );
          sb.append( Plane.format(_x) );
          sb.append( "," );
          sb.append( Plane.format(_y) );
          sb.append( ")" );

          return new String(sb);
      }
  }
  class Cookie implements Serializable {
      int _n;
      Location[] _vs;         // vertices
      Connector[] _es;       // edges: _es[i]: _vs[i]->_v[i+1]

      /** create a Cookie by vertices
       */
      public Cookie( Location[] vs ) {
          _n = vs.length;
          _vs = vs;
          _es = new Connector[ _n ];

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

      /** create a Cookie from a rectangle
       */
      public Cookie( Rectangle r ) {
          this( new Location[] {
              new Location( r.x0(), r.y0() ),
              new Location( r.x1(), r.y0() ),
              new Location( r.x1(), r.y1() ),
              new Location( 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 Location vertex( int idx ) {
          return _vs[idx];
      }

      /** the idx-th edge
       */
      public final Connector 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 Location closest( Connect ln ) {
          Location 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 Cookie must have no less than 3 vertices
       */
      final boolean isValid() {
          return _n >= 3;
      }

      /** a Cookie 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 Cookie 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 Cookie is negative.  We define the
       * Cookie 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;

          Location p = new Location(
              ( _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 Cookie intersects a line.
       */
      public final boolean intersects( Connector 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( Cookie cooks ) {
  /*
          Point c0 = center();
          double r0 = radius( c0 );
          Point c1 = cooks.center();
          double r1 = cooks.radius( c1 );

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

          return false;
      }


      /** dilate (expand) a Cookie by delta
       */
      public final Cookie dilate( double delta ) {
          double d = ( isNegative() ? delta : -delta );

          Vector ret = new Vector();
          Connect ln0 = _es[_n-1].shift( d );
          for ( int i=0; i<_n; i++ )
          {
              Connect 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 Connect )
                      ret.add( ln0.foot( _vs[i] ) );
                  else
                      ret.add( o );
              }

              ln0 = ln1;
          }

          return new Cookie( (Location[]) ret.toArray( new Location[0] ) );
      }

      /** check whether a Cookie contains a point.
       *
       * Algorithm of Wm Randolph Franklin.
       * http://www.ecse.rpi.edu/Homepages/wrf/research/geom/pnpoly.html
       */
      public final boolean contains( Location 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( Location 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 Cookie
       * when they intersect.
       */
      public final double contactDistance( Cookie cooks ) {
          Rectangle r0 = bound();
          Rectangle r1 = cooks.bound();
          double dmax = r0.width() + r1.width() + EPS;
          Location p = cooks.contactPos(
              new Cookie[]{ this },
              new Connector( new Location( dmax, 0 ), Location.ORIGIN ) );
          return p.x();
      }

      public final double contactDistanceObsolete( Cookie cooks ) {
          Rectangle r0 = bound();
          Rectangle r1 = cooks.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 )
          {
              Cookie cookst = cooks.translate( d, 0 );
              if ( intersects( cookst ) )
              {
                  d += dd;
                  continue;
              }

              for ( int i=0; i<cooks.n(); i++ )
              {
                  Location p = cookst.vertex(i);
                  Connector s = new Connector( p, new Location( cooks.x(i)+d, cooks.y(i) ) );
                  if ( intersects( s ) )
                  {
                      d += dd;
                      continue outer_loop;
                  }
              }

              ret = d;
              d -= dd;
          }

          return ret;
      }

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

          return p.distance( (Location) o );
      }

      /** move the origin of this Cookie along the Connector, until
       * the Cookie touchs one of cookss.  Return the point.
       */
      public final Location contactPos( Cookie[] cookss, Connector seg ) {
          double a = seg.a();
          double b = seg.b();
          double d = seg.p0().distance( seg.p1() );
          Location p0 = new Location(0,0), p1 = new Location(0,0);
          double x0 = seg.x0(), x1 = seg.x1(), y0 = seg.y0(), y1 = seg.y1();

          for ( int i=0; i<cookss.length; i++ )
          {
              Cookie cooks = cookss[i];

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

                  Connector s0 = new Connector( 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 );

                  Connector s0 = new Connector( p0, p1 );

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

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

      public final Location contactPosObsolete( Cookie[] cookss, Connector seg ) {
          double d = 0.5;
          double ret = 1.0;

      outer_loop:
          for ( double dd=d*0.5; dd>=DELTA; dd *= 0.5 )
          {
              Location p = seg.partition( d );
              Cookie cookst = translate( p.x(), p.y() );

              for ( int j=0; j<cookss.length; j++ )
                  if ( cookst.intersects( cookss[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( Cookie cooks,
                                              double dx, double dy ) {
          double ret = 0;
          for ( int j=0; j<cooks.n(); j++ )
          {
              double dist = distance( new Location( cooks.x(j)+dx, cooks.y(j)+dy ) );
              if ( dist < 0 )
                  ret += dist;
          }

          return ret;
      }

      /** try to find smaller distance than standard (puzzle like)
       */
      public final double minDistance( Cookie cooks, 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( cooks, 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( cooks, d-dd, 0 );
              double dist2 = _sum_neg_dist_by_offset( cooks, 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 Cookie transform( double[] mat ) {
          Location[] ps = new Location[ _n ];
          for ( int i=0; i<_n; i++ )
              ps[i] = _vs[i].transform( mat );
          return new Cookie( ps );
      }

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

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

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

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

      // let a Location at v0 move toward v1 for a distance no more than d
      public static Location _move_toward( Location v0, Location 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 Location( v0.x() + x*d/r, v0.y() + y*d/r );
      }

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

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

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

          // if the Cookie 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++ )
          {
              Location 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
              {
                  Location dir = new Location(
                      ( 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( Location 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 Cookie -- which means reverse the
       * vertex order
       */
      public Cookie negate() {
          Location[] vs = new Location[ _n ];
          vs[0] = _vs[0];
          for ( int i=1; i<_n; i++ )
              vs[i] = _vs[_n-i];

          return new Cookie( vs );
      }

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


      /** get the convex hull.  O(n^2)
       */
      public final Cookie 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 );
      }


      static final double EPS = Plane.EPS * 5.0;


      public Cookie[] crossify( Cookie cooks ) {

          Vector ncooks0 = new Vector();
          Vector ncooks1 = new Vector();
          boolean changed = false;

          for ( int i=0; i<_n; i++ )
              ncooks0.add( _vs[i] );
          for ( int i=0; i<cooks._n; i++ )
              ncooks1.add( cooks._vs[i] );


          for ( int i=0; i<ncooks0.size(); i++ )
          {
              int pi = ( (i>0)? i : ncooks0.size() ) - 1;

              Location s0 = (Location) ncooks0.elementAt( pi );
              Location t0 = (Location) ncooks0.elementAt( i );
              Connector st0 = new Connector( s0, t0 );   // the edge
              boolean changed0 = false;


              for ( int j=0; j<ncooks1.size(); j++ )
              {
                  int pj = ( (j>0)? j : ncooks1.size() ) - 1;

                  Location s1 = (Location) ncooks1.elementAt( pj );
                  Location t1 = (Location) ncooks1.elementAt( j );
                  Connector st1 = new Connector( s1, t1 );
                  boolean changed1 = false;


                  Object o = st0.intersection( st1 );

                  if ( null == o )
                      continue;

                  changed = true;

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


                      Connector seg = (Connector) o;

                      if ( seg.p0().distance( seg.p1() ) <= EPS )
                      {

                          o = seg.p0().midpoint( seg.p1() );
                      }
                      else
                      {

                          Location p0 = seg.p0();
                          Location 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;
                                  ncooks1.setElementAt( p0, pj );
                              }
                              else
                                  p0 = s1;
                          }
                          else if ( s1.distance( p1 ) <= EPS )
                          {
                              if ( p1 == s0 || p1 == t0 )
                              {
                                  s1 = p1;
                                  ncooks1.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;
                                  ncooks1.setElementAt( p0, j );
                              }
                              else
                                  p0 = t1;
                          }
                          else if ( t1.distance( p1 ) <= EPS )
                          {
                              if ( p1 == s0 || p1 == t0 )
                              {
                                  t1 = p1;
                                  ncooks1.setElementAt( p1, j );
                              }
                              else
                                  p1 = t1;
                          }
                          else
                              changed1 = true;


                          if ( changed1 )
                          {
                              if ( s1.distance( p0 ) > s1.distance( p1 ) )
                              {
                                  Location tmp = p0;
                                  p0 = p1;
                                  p1 = tmp;
                              }

                              if ( p1 != t1 )
                                  ncooks1.insertElementAt( p1, j );
                              if ( p0 != s1 )
                                  ncooks1.insertElementAt( p0, j );
                          }


                          if ( changed0 )
                          {
                              if ( s0.distance( p0 ) > s0.distance( p1 ) )
                              {
                                  Location tmp = p0;
                                  p0 = p1;
                                  p1 = tmp;
                              }

                              if ( p1 != t0 )
                                  ncooks0.insertElementAt( p1, i );
                              if ( p0 != s0 )
                                  ncooks0.insertElementAt( p0, i );
                          }

                      }
                  }

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

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


                      if ( p.distance( s0 ) <= EPS )
                          p = s0;
                      else if ( p.distance( t0 ) <= EPS )
                          p = t0;
                      else
                          changed0 = true;


                      if ( p.distance( s1 ) <= EPS )
                      {

                          if ( changed0 )
                          {

                              p = s1;
                          }
                          else
                          {
                              changed1 = true;
                              ncooks1.setElementAt( p, pj );
                          }
                      }
                      else if ( p.distance( t1 ) <= EPS )
                      {

                          if ( changed0 )
                          {
                              p = t1;
                          }
                          else
                          {
                              changed1 = true;
                              ncooks1.setElementAt( p, j );
                          }
                      }
                      else
                      {

                          changed1 = true;
                          ncooks1.insertElementAt( p, j );
                      }

                      // so for the first edge.
                      if ( changed0 )
                          ncooks0.insertElementAt( p, i );
                  }


                  if ( changed0 )
                      break;

                  if ( changed1 )
                      j--;
              }


              if ( changed0 )
                  i--;
          }

          if ( changed )
          {
              return new Cookie[] {
                  new Cookie( (Location[]) ncooks0.toArray( new Location[0] ) ),
                      new Cookie( (Location[]) ncooks1.toArray( new Location[0] ) ) };
          }

          return null;
      }

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


      public int[][] vericrossify( Cookie cooks ) throws Group7Exception {
          // allocate space first
          int[][] ret = new int[2][];
          ret[0] = new int[_n];
          ret[1] = new int[cooks._n];
          // first initial value to an illegal value
          for ( int i=0; i<_n; i++ )
              ret[0][i] = -3;
          for ( int j=0; j<cooks._n; j++ )
              ret[1][j] = -3;

          int shared = -1;
          // then set up all shared vertices
          for ( int j=0; j<cooks._n; j++ )
          {
              for ( int i=0; i<_n; i++ )
                  if ( _vs[i] == cooks._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 "
                                  + "cooks.vertex(" + ret[0][i]
                                  + ") and cooks.vertex(" + j + ")";
                          }

                          if ( ret[1][j] >= 0 )
                          {
                              s = "cooks.vertex(" + j + ") is shared by both "
                                  + "this.vertex(" + ret[1][j]
                                  + ") and this.vertex(" + i + ")";
                          }
                          throw new Group7Exception( s );
                      }
                      ret[0][i] = j;
                      ret[1][j] = i;
                      shared = i;
                  }
          }

          // no shared point
          if ( shared < 0 )
          {
              boolean b1 = contains( cooks._vs[0] );
              for ( int i=0; i<cooks._n; i++ )
              {
                  if ( contains( cooks._vs[i] ) != b1 )
                      throw new Group7Exception();

                  ret[1][i] = ( b1 ? INSIDE : OUTSIDE );
              }

              boolean b2 = cooks.contains( _vs[0] );
              // cookies cannot contain each other
              if ( b2 && b1 )
                  throw new Group7Exception();
              for ( int i=1; i<_n; i++ )
              {
                  if ( cooks.contains( _vs[i] ) != b2 )
                      throw new Group7Exception();
                  // if cooks 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 = cooks.contains(_vs[i]);
              if ( preset && b != prevalue )
                  throw new Group7Exception();
              preset = true;
              prevalue = b;
              ret[0][i] = ( b ? INSIDE : OUTSIDE );
          }

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

              boolean b = contains(cooks._vs[i]);
              if ( preset && b != prevalue )
                  throw new Group7Exception();
              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;


      public int compare( Cookie cooks ) {
          int in0 = 0;
          int out0 = 0;
          int in1 = 0;
          int out1 = 0;

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

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

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

      outer_loop1:  // check all of vertices of cooks1
          for ( int i=0; i<cooks._n; i++ )
          {
              Location p = cooks._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 cooks are in this Cookie, then this >= cooks
          if ( 0 == out1 )
              return CMP_SUPER;

          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( Cookie[] cookss, int[][] shared, int i, boolean forward ) {
          int n = ( forward ? cookss[1].next(i) : cookss[1].previous(i) );
          int m = shared[1][n];
          if ( m == OUTSIDE )
              return -1;
          if ( m >= 0 && !cookss[0].contains(
                   cookss[1].vertex(i).midpoint( cookss[1].vertex(n) ) ) )
              return -1;
          while ( m < 0 )
          {
              n = ( forward ? cookss[1].next(n) : cookss[1].previous(n) );
              m = shared[1][n];

          }
          return m;
      }


      public Cookie[][] cutsets( Cookie cooks ) throws Group7Exception {

          // adding cross points into the Cookie
          Cookie[] cookss = crossify( cooks );
          if ( cookss == null )
              return null;

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

          int[][] shared = cookss[0].vericrossify( cookss[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 = cookss[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 = cookss[0]._find_first_unused( used, first );
              if ( first < 0 )
                  break;
              // the vector to store the vertices of the current Cookie
              Vector vlist = new Vector();
              // yet not know which set the Cookie belongs to
              int direction = -1;
              if ( shared[0][first] < 0 )
                  direction = ( shared[0][first]==INSIDE ? 0 : 1 );

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

                  // assert !used[i];
                  if ( used[i] )
                  {

                      throw new Group7Exception();
                  }
                  used[i] = true;

                  i = cookss[0].next(i);
                  // if the start point is met, end this Cookie
                  if ( i == first )
                      break;

                  int j = shared[0][i];

                  if ( j < 0 )
                  {
                      int dir = ( j==INSIDE ? 0 : 1 );
                      if ( direction == 1-dir )
                          throw new Group7Exception();
                      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( cookss, shared, j, true );
                      int bdst = _arc_to( cookss, shared, j, false );


                      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( cookss[0]._vs[i] );

                          // traverse backward along cooks1 util another
                          // shared vertex is found
                          for (;;)
                          {
                              j = cookss[1].previous(j);
                              if ( shared[1][j] >= 0 )
                                  break;
                              // add vertices traversed to the Cookie
                              vlist.add( cookss[1]._vs[j] );
                                                        }

                          i = shared[1][j];
                      }
                      else if ( fdst >= 0 )
                      {
                          vlist.add( cookss[0]._vs[i] );


                          // until another shared vertex is found
                          for (;;)
                          {
                              j = cookss[1].next(j);
                              if ( shared[1][j] >= 0 )
                                  break;
                              vlist.add( cookss[1]._vs[j] );

                          }

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

                  if ( i == first )
                      break;
              }

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

              Cookie result = new Cookie(
                  (Location[])vlist.toArray( new Location[0] ) );

              // crossing type cannot be determined
              if ( direction < 0 )
              {

                  int cmp = result.compare( cookss[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 Group7Exception();

              }

              // a Cookie is finished
              plist[direction].add( result );

          }

          // all cookies are finished
          return new Cookie[][] {
              (Cookie[]) plist[0].toArray( new Cookie[0] ),
                  (Cookie[]) plist[1].toArray( new Cookie[0] ) };
      }
  }
  /**
   * A rectangle whose edges are parallel with x- and y- axes.
   */
  class Rectangle implements Serializable {
      double _x0, _y0, _width, _height;

      public Rectangle( double x0, double y0, double width, double height ) {
          _x0 = x0;
          _y0 = y0;
          _width = width;
          _height = height;
      }

      public final double x0() {
          return _x0;
      }

      public final double x1() {
          return _x0 + _width;
      }

      public final double y0() {
          return _y0;
      }

      public final double y1() {
          return _y0 + _height;
      }

      public final double width() {
          return _width;
      }

      public final double height() {
          return _height;
      }

      public final boolean intersects( Rectangle r ) {
          return ( x1() >= r.x0() || x0() <= r.x1() )
              && ( y1() >= r.y0() || y0() <= r.y1() );
      }

      public Rectangle union( Rectangle r ) {
          double x0 = Math.min( x0(), r.x0() );
          double x1 = Math.max( x1(), r.x1() );
          double y0 = Math.min( y0(), r.y0() );
          double y1 = Math.max( y1(), r.y1() );
          return new Rectangle( x0, y0, x1-x0, y1-y0 );
      }

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

          sb.append( "[(" );
          sb.append( Plane.format( _x0 ) );
          sb.append( "," );
          sb.append( Plane.format( _y0 ) );
          sb.append( ") " );
          sb.append( Plane.format( _width ) );
          sb.append( "/" );
          sb.append( Plane.format( _height ) );
          sb.append( "]" );

          return new String(sb);
      }
  }
  /**
   * A connector on the xy-plane that connects two locations
   */

  class Connector extends Connect implements Serializable {
      Location _v0, _v1;

      /** given two points, connect them*/

      public Connector( Location v0, Location 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 Location p0() {
          return _v0;
      }

      public final Location p1() {
          return _v1;
      }



      public final Location partition( double r ) {
          double r1 = 1 - r;
          return new Location( _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_Connector( Location p0, Location p1 ) {
          if ( p0.equals( p1 ) )
              return p0;
          return new Connector( p0, p1 );
      }

      /** get the intersection of two connectors -- either a point,
       * a line segment, or null indicating they do not intersect.*/

      public Object intersection( Connector s ) {

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

          if ( p instanceof Connect )
          {

              double a0, a1, b0, b1;

              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_Connector( _v1, s._v0 );
                  return _create_Connector( _v0, s._v0 );
              }

              if ( _is_between( b1, a0, a1, Plane.EPS ) )
              {
                  if ( _is_between( a1, b0, b1, Plane.EPS ) )
                      return _create_Connector( _v1, s._v1 );
                  return _create_Connector( _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 = ((Location)p).x();
              double y = ((Location)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;
          }
      }



      public final double distance( Location p ) {
          Location 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 ) );

      }

        public final double distance( Connector 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);
      }
  }
  class Plane {

      public static final double EPS = 1E-10;
      static java.util.Random rand = new java.util.Random();
      static NumberFormat numformat;

      static {
          numformat = NumberFormat.getInstance();
          numformat.setMinimumFractionDigits( 1 );
          numformat.setMaximumFractionDigits( 4 );
          numformat.setMinimumIntegerDigits( 1 );
      }

      /** convert a double value to a string
       */
      public static String format( double x ) {
          return numformat.format( x );
      }

      /** check the sign of a number*/
      public static int sign( double x ) {
          if ( x > EPS )
              return 1;
          if ( x < -EPS )
              return -1;
          return 0;
      }


      public static final double[] transmatrix( double x0, double y0,
                                                double alpha ) {
          double c = Math.cos( alpha );
          double s = Math.sin( alpha );
          return new double[]{ c, s, -x0, -s, c, -y0 };
      }

      /*perform transformation of the matrix*/
      public static final double[] transmatrix( Location p0, Location p1 ) {
          double d = p0.distance( p1 );
          if ( d <= EPS )
              return new double[] { 1.0, 0.0, -p0.x(), 0.0, 1.0, -p0.y() };

          double c = ( p1.x() - p0.x() )/d;
          double s = ( p1.y() - p0.y() )/d;
          return new double[] { c, s, -p0.x(), -s, c, -p0.y() };
      }

      /** get a rotation of the matrix
       */
      public static final double[] rotatematrix( double x0, double y0,
                                                 double alpha ) {
          double c = Math.cos( alpha );
          double s = Math.sin( alpha );
          return new double[]{ c, -s, (1-c)*x0+s*y0, s, c, (1-c)*y0-s*x0 };
      }
  }
