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

package CookieCutter.g0;
import CookieCutter.*;
import CookieCutter.g0.*;
import java.util.Vector;
import java.io.*;

public class Group7Player5 implements IFCPlayer{
    public static final double DELTA = 1E-5;
    double _sh;
    int _nc;

    /** the strategy positioning the cookiees
     */
    public class Strategy {
        double[] x;
        double[] y;
        double[] angle;
        double rbound = 0;

        // the following fields are used to store intermediate data
        int n;               // the number of available solutions
        int[] stage = null;  // the (last) stage that this solution is useful
                             // -1 if the solution is still open
        // int[] idx = null; // the backtrack index
        Cookie[] cookss;       // use spaces to trade-off time
        Rectangle[] bds;         // use spaces to trade-off time
        double bbound = 0;   // the y-dimensional bound
        double bxbound = 0;

        public Strategy( int _n ) {
            x = new double[_n];
            y = new double[_n];
            angle = new double[_n];

        }

        public Strategy( double[] _x, double[] _y, double[] _angle, int _n ) {
            x = _x;
            y = _y;
            angle = _angle;
            n = _n;
        }
    }

    /** the constructor
     */
    public Group7Player5( double height, int ncookies ) {
        _sh = height;
        _nc = ncookies;
    }
    //val_st
    public Group7Player5() {

        }
        //val_end1
//val_st2

        CookieCutter _game;                   // the mediator class
    Vertex[][]   _cookies;                // original cookie shapes
    int          _copies;                 // number of cookies
    String       _name = "Cream and Cookies Adventure";

    /** register the game and get game parameters
     */
    public void register( CookieCutter game ) throws Exception {
        _game = game;
        _cookies = game.cookieshapes();
        _copies = game.cookieCopies();
    }


    public String name() throws Exception {
        return _name;
    }


    /** the game is played by the program
     */
    public boolean interactive() throws Exception {
        return false;
    }

    /** check the validity of a solution, use system intersectio functions
     */
    public boolean valid( Cookie cooks, Group7Player5.Strategy soln ) {
        try
        {

            int n = cooks.n();
            for ( int i=0; i<_copies; i++ )
            {
                Cookie cooks0 = cooks.place( soln.x[i], soln.y[i], soln.angle[i] );

                Vertex[] vt0 = new Vertex[n];
                for ( int k=0; k<n; k++ )
                {
                    vt0[k] = new Vertex( cooks0.x(k), cooks0.y(k) );
                    if ( cooks0.x(k) <= 0 || cooks0.x(k) >= soln.rbound
                         || cooks0.y(k) <= 0 || cooks0.y(k) >= 1 )
                        return false;
                }

                for ( int j=0; j<i; j++ )
                {
                    Cookie cooks1 = cooks.place( soln.x[j], soln.y[j], soln.angle[j] );
                    Vertex[] vt1 = new Vertex[n];
                    for ( int k=0; k<n; k++ )
                        vt1[k] = new Vertex( cooks1.x(k), cooks1.y(k) );

                    for ( int k=0, pk=n-1; k<n; pk=k++ )
                        for ( int l=0, pl=n-1; l<n; pl=l++ )
                            if ( _game.intersects( vt0[pl], vt0[l],
                                                   vt1[pk], vt1[k] ) )
                                return false;

                }


            }
        }
        catch ( Exception e )
        {
            return false;
        }

        return true;


    }

    /** the abstract method of solver that the derived class must override
     */
     public Group7Player5.Strategy solve(
        Group7Player5 prob, Cookie cooks )
      {
           return prob.strat1( cooks );
}
    /** generate a Cookie object
     */
    public static Cookie createPolygon( Vertex[] vet ) throws Exception {
        Location[] pts = new Location[ vet.length ];
        for ( int i=0; i<pts.length; i++ )
            pts[i] = new Location( vet[i].xpos(), vet[i].ypos() );
        return new Cookie( pts );
    }

    /** the moves generator.
     */
    public Move[] moves() throws Exception {
        Move[] ret = new Move[ _cookies.length ];

        // create the problem
        Group7Player5 prob = new Group7Player5( 1.0, _copies );

        // for each shape call the problem solver
        for ( int i=0; i<_cookies.length; i++ )
        {
            // find the centroid, and re-center the Cookie to the centroid,
            // the dilate the Cookie by DELTA for a small gap
            Vertex centroid = _game.centroid( _cookies[i] );
            Cookie cooks = createPolygon( _cookies[i] ).translate(
                -centroid.xpos(), -centroid.ypos() ).dilate( DELTA );

            // call solver to solve this problem
            Group7Player5.Strategy soln;
            try
            {
                soln = solve( prob, cooks );
            }
            catch( Exception e )
            {
                e.printStackTrace();
                soln = null;
            }

            ret[i] = new Move(_copies);
            if ( null == soln )
            {
                // the solver can not find a solution, what can we do?
                System.err.println(
                    "Fatal error: unable to place cookie #" + i );
                ret[i].setRightBoundary( 0 );
                continue;
            }

            // we now have a solution.  Generate the moves
            for ( int j=0; j<_copies; j++ )
            {
                ret[i].setCookiePosition(
                    j, new Vertex( soln.x[j], soln.y[j] ), soln.angle[j] );
            }
            ret[i].setRightBoundary( soln.rbound );
        }

        return ret;
    }


      //val_end2


    /**
     * This solver finds a minimum bounding box and tile it.
     */
   public Strategy strat2( Cookie cooks ) {

        double rbound = 1E100;
        double angle = 0;
        Rectangle bd = null;
        int n = 0;

        // System.out.println( "Bound: " + bound.toString() );

        for ( int j=0; j<360; j++ )
        {
            double angle1 = Math.toRadians( j );
            Cookie cooks1 = cooks.rotate( angle1 );
            Rectangle bd1 = cooks1.bound();

            int n1 = (int) Math.floor( _sh / bd1.height() );
            if ( n1 <= 0 )
                continue;

            double rbound1 = ( (_nc-1) / n1 + 1 ) * bd1.width();

            if ( rbound1 < rbound )
            {
                n = n1;
                angle = angle1;
                bd = bd1;
                rbound = rbound1;
            }
        }

        if ( null == bd )
            return null;

        Strategy ret = new Strategy( _nc );

        for ( int j=0; j<_nc; j++ )
        {
            ret.y[j] = (j % n) * bd.height() - bd.y0();
            ret.x[j] = (j / n) * bd.width() - bd.x0();
            ret.angle[j] = angle;
        }

        ret.rbound = rbound;
        return ret;
    }


    /** compute the minimum distances in with different angles
     * n must be even
     */
    static double[][] minDistances( Cookie cooks, int n ) {
        double[][] ret = new double[n][n];
        // int n2 = n/2;
        for ( int i=0; i<n; i++ )
            for ( int j=0; j<n; j++ )
            {
                ret[i][j] =
                    cooks.rotate( i*Math.PI*2/n ).contactDistance(
                        cooks.rotate( j*Math.PI*2/n ) );
            }

        return ret;
    }

    /** compute the minimum box for pairwise fit
     */
    Strategy minPair( Cookie cooks, double[][] mindist ) {
        final int m = 24;
        int n = mindist.length;
        Strategy ret = new Strategy(2);
        double eval = 1E10;
        for ( int i=0; i<n/2; i++ )
        {
            double angle0 = i*Math.PI*2/n;
            Cookie cooks0 = cooks.rotate( angle0 );
            for ( int j=0; j<n; j++ )
            {
                double angle1 = j*Math.PI*2/n;
                Cookie cooks1 = cooks.place( mindist[i][j], 0, angle1 );

                for ( int k=0; k<m; k++ )
                {
                    double angle2 = k*Math.PI*2/m;
                    Rectangle bd0 = cooks0.rotate( angle2 ).bound();
                    Rectangle bd1 = cooks1.rotate( angle2 ).bound();
                    Rectangle bd = bd0.union( bd1 );

                    int c = (int) Math.floor( _sh / bd.height() );
                    if ( c <= 0 )
                        continue;
                    double ev = bd.width() / c;
                    if ( ev < eval )
                    {
                        eval = ev;
                        ret.x[0] = -bd.x0();
                        ret.y[0] = -bd.y0();
                        ret.x[1] = mindist[i][j]*Math.cos( angle2 ) - bd.x0();
                        ret.y[1] = mindist[i][j]*Math.sin( angle2 ) - bd.y0();
                        ret.angle[0] = angle0 + angle2;
                        ret.angle[1] = angle1 + angle2;
                    }
                }
            }
        }
        return ret;
    }

    /**
     * This solver tries to find a minimum bouding box for a pair of polygons
     */
    public Strategy strat1( Cookie cooks ) {
        Cookie cooks_orig = cooks;
        cooks = cooks.dilate( DELTA );

        int nangle = 24;
        double[][] mindist = minDistances( cooks, nangle );
        Strategy mp = minPair( cooks, mindist );

        Strategy ret = new Strategy( _nc );
        Rectangle bd0 = cooks.place( mp.x[0], mp.y[0], mp.angle[0] ).bound();
        Rectangle bd1 = cooks.place( mp.x[1], mp.y[1], mp.angle[1] ).bound();
        Rectangle bd = bd0.union( bd1 );
        double xdist = bd.width();
        double ydist = bd.height();

        // System.out.println( " xdist: " + xdist + " ydist: " + ydist );

        int n = (int) Math.floor( _sh / ydist );
        if ( n <= 0 )
          return strat2( cooks );

        int lines = _nc/2/n;
        int m = lines*n;
        for ( int j=0; j<m; j++ )
        {
            ret.y[j+j] = (j % n) * ydist + mp.y[0];
            ret.x[j+j] = (j / n) * xdist + mp.x[0];
            ret.angle[j+j] = mp.angle[0];
            ret.y[j+j+1] = (j % n) * ydist + mp.y[1];
            ret.x[j+j+1] = (j / n) * xdist + mp.x[1];
            ret.angle[j+j+1] = mp.angle[1];
        }

        double rbound = lines * xdist;
        if ( _nc > m + m )
        {
            Strategy soln = new Group7Player5(
                1.0, _nc-m-m ).strat2( cooks );
            for ( int i=0; i<_nc-m-m; i++ )
            {
                ret.x[i+m+m] = soln.x[i]+rbound;
                ret.y[i+m+m] = soln.y[i];
                ret.angle[i+m+m] = soln.angle[i];
            }
            rbound += soln.rbound;
        }
        ret.rbound = rbound;
        compress( ret, cooks_orig );
        return ret;
    }


    public Strategy choices( Strategy hist, Cookie cooks,
                             Strategy[] prevc, int stage,
                             double[][] mindist, double yres ) {

        final int maxc = 1000;

        double[] xc = new double[maxc];
        double[] yc = new double[maxc];
        double[] ac = new double[maxc];
        Cookie[] cookss = new Cookie[maxc];
        Rectangle[] bds = new Rectangle[maxc];
        int[] sc = new int[maxc];
        int nc = 0;
        double radius = cooks.radius( Location.ORIGIN );
        double sqrad2 = radius * radius * 4.0;

        // first kind of choices: polygons that attached to the last Cookie
        // try all combinations of angles
        if ( stage > 0 )
        {
            // compute the coordinates and angle of the prevoius Cookie
            double x0 = hist.x[stage-1];
            double y0 = hist.y[stage-1];
            double angle0 = hist.angle[stage-1];

        loop1:
            for ( int i=0; i<mindist.length; i++ )
            {
                // the actual direction of the new Cookie is the
                // difference of angle the previous Cookie and its
                // anticipated angle w.r.t. the new Cookie.
                double angle1 = i*2*Math.PI/mindist.length;
                double cos = Math.cos( angle0-angle1 );
                double sin = Math.sin( angle0-angle1 );

            loop2:
                for ( int j=0; j<mindist.length; j++ )
                {
                    double angle2 = j*2*Math.PI/mindist.length;
                    double dist = mindist[i][j];
                    double x = dist * cos + x0;
                    double y = dist * sin + y0;
                    double angle = angle0-angle1+angle2; // the actual angle

                    if ( x <= 0 || y <= 0 || y >= _sh )
                        continue;

                    Cookie cookstmp = cooks.place( x, y, angle );
                    Rectangle bd = cookstmp.bound();

                    if ( bd.x0() < 0 || bd.y0() < 0 || bd.y1() >= _sh )
                        continue;

                    for ( int k=0; k<stage-1; k++ )
                    {
                        if ( Location.sqdistance( x, y, hist.x[k], hist.y[k] )
                             <= sqrad2 && cookstmp.intersects( hist.cookss[k] ) )
                            continue loop2;
                    }

                    xc[nc] = x;
                    yc[nc] = y;
                    ac[nc] = angle;
                    sc[nc] = -1;
                    cookss[nc] = cookstmp;
                    bds[nc] = bd;

                    if ( ++nc >= maxc )
                        break loop1;
                }
            }
        }

        // set up the new bxbound. (bottom extension bound)
        double bxbound = cooks.radius( new Location(0,0) ) * 2;
        if ( stage > 0 )
            bxbound += prevc[stage-1].bbound;

        // the second kind of choices: position available at the
        // left-most column
        if ( nc < maxc )
        {
            double oldbxb = 0;
            if ( stage > 0 )
                oldbxb = prevc[stage-1].bxbound;
            // System.out.println( "Oldbxb: " + oldbxb );

        loop4:
            for ( int j=0; j<mindist.length; j++ )
            {
                double angle = j*2*Math.PI/mindist.length;
                Cookie cookstmp0 = cooks.rotate( angle );
                Rectangle bd0 = cookstmp0.bound();
                double ymax = Math.min( bxbound, _sh - bd0.height() );

            loop5:
                for ( double yoff=oldbxb; yoff<=ymax; yoff += yres )
                {
                    double x = -bd0.x0();
                    double y = yoff-bd0.y0();
                    Cookie cookstmp = cookstmp0.translate( x, y );
                    Rectangle bd = new Rectangle( 0, yoff,
                                                  bd0.width(), bd0.height() );

                    for ( int k=0; k<stage; k++ )
                    {
                        if ( Location.sqdistance( x, y, hist.x[k], hist.y[k] )
                             <= sqrad2 && cookstmp.intersects( hist.cookss[k] ) )
                            continue loop5;
                    }

                    xc[nc] = x;
                    yc[nc] = y;
                    ac[nc] = angle;
                    sc[nc] = -1;
                    cookss[nc] = cookstmp;
                    bds[nc] = bd;

                    if ( ++ nc >= maxc )
                        break loop4;
                }
            }
        }

        prevc[stage] = new Strategy( xc, yc, ac, nc );
        prevc[stage].stage = sc;
        prevc[stage].cookss = cookss;
        prevc[stage].bds = bds;
        prevc[stage].bxbound = bxbound;

        // the third kind of choices: valid previous choices
        if ( nc < maxc )
        {
            // previous choices
        loop3:
            for ( int i=stage-1; i>=0; i-- )
                for ( int j=0; j<prevc[i].n; j++ )
                    if ( prevc[i].stage[j] < 0 )
                    {
                        xc[nc] = prevc[i].x[j];
                        yc[nc] = prevc[i].y[j];
                        ac[nc] = prevc[i].angle[j];
                        sc[nc] = i;
                        cookss[nc] = prevc[i].cookss[j];
                        bds[nc] = prevc[i].bds[j];

                        if ( ++nc >= maxc )
                            break loop3;
                    }
        }

        Strategy ret = new Strategy( xc, yc, ac, nc );
        ret.stage = sc;
        ret.cookss = cookss;
        ret.bds = bds;

        return ret;
    }

    /** choose the idx-th choices and update the database
     */
    public void choose( Strategy choices, int idx,
                        Strategy hist, Cookie cooks,
                        Strategy[] prevc, int stage ) {

        double x = choices.x[idx];
        double y = choices.y[idx];
        double angle = choices.angle[idx];
        Cookie ncooks = cooks.place( x, y, angle );
        double radius = cooks.radius( Location.ORIGIN ) + Plane.EPS;
        double sqrad2 = radius * radius * 4.0;

        Rectangle r = ncooks.bound();

        hist.x[stage] = x;
        hist.y[stage] = y;
        hist.angle[stage] = angle;
        hist.cookss[stage] = ncooks;
        hist.bds[stage] = r;

        if ( r.x1() > hist.rbound )
            hist.rbound = r.x1();
        if ( r.y1() > hist.bbound )
            hist.bbound = r.y1();

        prevc[stage].rbound = hist.rbound;
        prevc[stage].bbound = hist.bbound;

        // eliminate now-invalid previous choices.
        for ( int i=0; i<=stage; i++ )
        {
            Strategy prevci = prevc[i];
            for ( int j=0; j<prevc[i].n; j++ )
                if ( prevci.stage[j] < 0 && Location.sqdistance(
                         x, y, prevci.x[j], prevci.y[j] ) <= sqrad2
                     && ncooks.intersects( prevci.cookss[j] ) )
                {
                    prevc[i].stage[j] = stage;
                }
        }
    }

    /** for backtrack algorithm
     */
    public void backtrack( Strategy[] prevc, int stage ) {
        for ( int i=0; i<stage; i++ )
            for ( int j=0; j<prevc[i].n; j++ )
                if ( prevc[i].stage[j] == stage )
                    prevc[i].stage[j] = -1;
    }


    public void compress( Strategy soln, Cookie cooks ) {
        final int n2dir = 32;
        double[] alpha = new double[ n2dir * 2 + 1 ];
        Connector[] dirs = new Connector[ n2dir * 2 + 1 ];

        // create testing directions
        alpha[0] = Math.PI;
        dirs[0] = new Connector( Location.ORIGIN, new Location( -10, 0 ) );
        for ( int i=1; i<=n2dir; i++ )
        {
            alpha[i+i-1] = Math.PI-i*Math.PI*0.5/n2dir;
            double cos = Math.cos(alpha[i+i-1]);
            double sin = Math.sin(alpha[i+i-1]);
            dirs[i+i-1] = new Connector( Location.ORIGIN,
                                       new Location( 10*_sh*cos, 10*_sh*sin ) );
            alpha[i+i] = -alpha[i+i-1];
            dirs[i+i] = new Connector( Location.ORIGIN,
                                     new Location( 10*cos, -10*sin ) );
        }

        soln.cookss = new Cookie[_nc];
        for ( int i=0; i<_nc; i++ )
            soln.cookss[i] = cooks.place( soln.x[i], soln.y[i], soln.angle[i] );

        Cookie box = new Cookie( new Rectangle( 0, 0, _nc*10, _sh ) );
        Cookie[] rest = new Cookie[_nc];
        for ( int i=0; i<_nc; i++ )
            rest[i] = soln.cookss[i];

        for ( int k=0; k<4; k++ )
        {
            for ( int i=0; i<_nc; i++ )
            {
                rest[i] = box;
                Cookie cooks0 = soln.cookss[i];

                double eval = 0;
                double x = 0;
                double y = 0;

                for ( int j=0; j<dirs.length; j++ )
                {
                    Location p = cooks0.contactPos( rest, dirs[j] );
                    double ev = 0.01*p.x()*p.x() +  p.y() * p.y();
                    if ( ev > eval )
                    {
                        eval = ev;
                        x = p.x();
                        y = p.y();
                    }
                }

                double d = Math.sqrt(x*x+y*y);
                if ( d > DELTA * 5.0 )
                {
                    x -= DELTA * x / d;
                    y -= DELTA * y / d;

                    soln.x[i] += x;
                    soln.y[i] += y;
                    soln.cookss[i] = cooks0.translate( x, y );
                }

                rest[i] = soln.cookss[i];
            }
        }

        double rbound = 0;
        for ( int i=1; i<soln.cookss.length; i++ )
        {
            double d = soln.cookss[i].bound().x1();
            if ( d > rbound )
                rbound = d;
        }

        soln.rbound = rbound;
    }

/*
 * The test program
 */

public boolean valid1( Cookie cooks, Group7Player5.Strategy soln ) {

        for ( int i=0; i<_nc; i++ )
        {
            Cookie cooks0 = cooks.place( soln.x[i], soln.y[i], soln.angle[i] );

            Rectangle r = cooks0.bound();

            if ( r.x0() <= 0 || r.x1() >= soln.rbound
                 || r.y0() <= 0 || r.y1() >= _sh )
            {
                System.out.println( "Warning: cookie #" + i
                                    + " is out of the bounding box." );
                return false;
            }

            for ( int j=0; j<i; j++ )
            {
                Cookie cooks1 = cooks.place( soln.x[j], soln.y[j], soln.angle[j] );
                if ( cooks0.intersects( cooks1 ) )
                {
                    System.out.println( "Warning: cookie #" + i
                                        + " intersects with " + j );
                    return false;
                }
            }
        }

        return true;
    }

    public static void main( String[] args ) {
        Environment pane = Environment.create( "Cookie Test", 980, 840 );
        Environment.main = pane;

        double xdist = 4.8;
        double ydist = 1.05;
        double nsol = 3;

        pane.setRange( -0.05, xdist+0.05, -0.05, ydist*3+1 );

        Cookie[] cookss = null;

        if ( 1 == args.length )
        {
            cookss = Environment.readGameFile( args[0] );

            if ( null == cookss )
            {
                System.err.println( "File not found: " + args[0] );
                return;
            }
        }

        int[] base_color = new int[]{ 0xff0000, 0x00ff00, 0x808000 };

        int n = 10;
        for ( int i=0; cookss != null && i<cookss.length; i++ )
        {
            Location centroid = cookss[i].centroid();
            Cookie cooks = cookss[i].translate( -centroid.x(), -centroid.y() );
            Cookie[] hist = new Cookie[n];

            Group7Player5 prob = new Group7Player5( 1.0, n );

            Strategy soln[] = {

                prob.strat1( cooks.dilate(DELTA) )
            };

            for ( int k=0; k<nsol; k++ )
            {
                pane.draw( new Rectangle( 0, ydist*k, xdist, 1 ), 0x808080 );

                if ( null == soln[k] )
                    pane.draw( "Strategy " + k + " does not exist",
                               base_color[k] );
                else if ( !prob.valid1( cooks, soln[k] ) )
                    pane.draw( "Strategy " + k + " is invalid",
                               base_color[k] );
            }

            for ( int j=0; j<n; j++ )
            {
                for ( int k=0; k<nsol; k++ )
                {
                    if ( soln[k] != null )
                    {
                        double yoff = ydist * (nsol-k-1);
                        Cookie rcooks = cooks.place( soln[k].x[j], soln[k].y[j],
                                                soln[k].angle[j] );
                        pane.draw( rcooks.translate( 0, yoff ),
                                   0xc0000000+base_color[k] + (j*95+n)%256 );

                        pane.draw( new Connector(
                                       new Location( soln[k].rbound, yoff ),
                                       new Location( soln[k].rbound, yoff+1 ) ),
                                   0 );
                    }
                }
            }

            System.out.println( "Try: " + cooks.toString() );

            for ( int k=0; k<2; k++ )
                for ( int j=0; j<4; j++ )
                {
                    Environment.main.draw( cooks.place( j+k*4, 3.5, Math.PI*0.5*j ),
                                       0xb0b0b0 );
                    Environment.main.draw( cooks.place( j+k*4, 3.5, Math.PI*0.5*k ),
                                       0 );

                    Location p = perfectMatch( cooks.rotate( Math.PI*0.5*k ),
                                            cooks.rotate( Math.PI*0.5*j ) );
                    if ( p != null )
                    {
                        System.out.println( "Matching: " + p.toString() );
                        Environment.main.draw(
                            cooks.place( j+k*4+p.x(), 3.5+p.y(),
                                      Math.PI*0.5*j ), 0xffff00 );
                    }
                }


            pane.waitClick();
            pane.clear();
        }

        Cookie E = new Cookie( new Location[] {
            new Location(0,0), new Location(0,1),  new Location(0.8,1),
            new Location(0.8,0.8), new Location(0.2,0.8), new Location(0.2,0.6),
            new Location(0.6,0.6), new Location(0.6,0.4), new Location(0.2,0.4),
            new Location(0.2,0.2), new Location(0.8,0.2), new Location(0.8,0) } );
        Cookie N = new Cookie( new Location[] {
            new Location(0,0), new Location(0,1), new Location(0.3,1),
            new Location(0.6,0.2), new Location(0.6,1), new Location(0.8,1),
            new Location(0.8,0), new Location(0.5,0),
            new Location(0.2,0.8), new Location(0.2,0) } );
        Cookie D1 = new Cookie( new Location[] {
            new Location(0,0), new Location(0,1), new Location(0.2,1),
            new Location(0.2,0) } );
        Cookie D2 = new Cookie( new Location[] {
            new Location(0.2,1), new Location(0.4,0.95), new Location(0.7,0.8),
            new Location(0.8,0.5), new Location(0.7,0.2), new Location(0.4,0.05),
            new Location(0.2,0), new Location(0.2,0.2), new Location(0.5,0.3),
            new Location(0.6,0.5), new Location(0.5,0.7), new Location(0.2,0.8 ) } );

        pane.draw( E.translate( 1, 1 ), 0xffff0000 );
        pane.draw( N.translate( 2, 1 ), 0xff00ff00 );
        pane.draw( D1.translate( 3, 1 ), 0xff0000ff );
        pane.draw( D2.translate( 3, 1 ), 0xff0000ff );
    }


/*
 * The following functions are experimental.
 */

    private static double[] __interval_and( double[] iv0, int n0,
                                            double[] iv1, int n1 ) {
        double[] iv = new double[ n0+n1 ];
        int niv = 0;

    outer_loop:
        for ( int i=0, j=0;;)
        {
            while ( iv0[i+1] <= iv1[j] )
            {
                i += 2;
                if ( i >= n0-1 )
                    break outer_loop;
            }

            while ( iv0[i] >= iv1[j+1] )
            {
                j += 2;
                if ( j >= n1-1 )
                    break outer_loop;
            }

            iv[niv++] = Math.max( iv0[i], iv1[j] );
            iv[niv++] = Math.min( iv0[i+1], iv1[j+1] );


            if ( iv0[i+1] >= iv1[j+1] )
            {
                j += 2;
                if ( j >= n1-1 )
                    break;
            }
            else
            {
                i += 2;
                if ( i >= n0-1 )
                    break;
            }
        }

        if ( niv == 0 )
            return null;

        if ( niv == iv.length )
            return iv;
        double[] tmp = new double[niv];
        System.arraycopy( iv, 0, tmp, 0, niv );
        return tmp;
    }

    private static double __interval_area( double[] data, int ndata ) {
        double ret = 0;
        for ( int i=0; i<ndata-1; i++ )
            ret += data[i+1]-data[i];
        return ret;
    }


    private static double[] _compute_interval(
        Cookie cooks0, Cookie cooks1, double xinf, double xsup, double y ) {

        double[] itv = new double[cooks0.n()+2];
        double[] ret = new double[] { xinf, xsup };

        for ( int i=0; i<cooks1.n(); i++ )
        {
            Connector seg = new Connector(
                new Location( cooks1.x(i)+xinf, cooks1.y(i)+y ),
                new Location( cooks1.x(i)+xsup, cooks1.y(i)+y ) );

            int nitv = 0;

            itv[nitv++] = xinf - cooks1.x(i);
            for ( int j=0; j<cooks0.n(); j++ )
            {
                Object o = seg.intersection( cooks0.edge(j) );
                if ( null == o || ! ( o instanceof Location ) )
                    continue;
                Location p = (Location) o;

                itv[nitv++] = p.x() - cooks1.x(i);
            }
            itv[nitv++] = xsup - cooks1.x(i);

            if ( ( nitv & 1 ) == 1 )
            {
                System.out.println( "odd number" );
                continue;
            }

            java.util.Arrays.sort( itv, 0, nitv );

            ret = __interval_and( ret, ret.length, itv, nitv );

            if ( ret == null )
                return null;

        }

        return ret;
    }

    private static double __smallest_abs( Cookie cooks0, Cookie cooks1,
                                          double[] itv, double y ) {
        double ret = 1E100;
        for ( int i=0; i<itv.length; i++ )
        {
            if ( Math.abs( itv[i] ) < Math.abs( ret ) )
            {
                double x = ( (i&1)==1 ? itv[i]-DELTA : itv[i]+DELTA );
                if ( !cooks1.translate( x, y ).intersects( cooks0 ) )
                    ret = itv[i];
            }
        }

        return ret;
    }

    public static Location perfectMatch( Cookie cooks0, Cookie cooks1 ) {
        Rectangle bd0 = cooks0.bound();
        Rectangle bd1 = cooks1.bound();
        double ysup = bd0.y1() - bd1.y0() - DELTA;
        double yinf = bd0.y0() - bd1.y1() + DELTA;
        double xsup = ( bd0.x1() - bd1.x0() ) * 2.01 + DELTA;
        double xinf = ( bd0.x0() - bd1.x1() ) * 2.01 - DELTA;
        Location ret = null;
        double d = 1E100;

        final int np = 128;
        for ( int k=0; k<np; k++ )
        {
            double x, y = (k+0.5)*(ysup-yinf)/np + yinf;

            // ___k = 0;
            double[] itv1 =
                _compute_interval( cooks0, cooks1, xinf, xsup, y );
            if ( itv1 == null )
                continue;

            // ___k = k;

            double[] itv2 =
                _compute_interval( cooks1, cooks0, -xsup, -xinf, -y );
            if ( itv2 == null )
                continue;

            for ( int i=itv2.length/2-1; i>=0; i-- )
            {
                double tmp = itv2[i];
                itv2[i] = -itv2[itv2.length-i-1];
                itv2[itv2.length-i-1] = -tmp;
            }

            double[] itv =
                __interval_and( itv1, itv1.length, itv2, itv2.length );


            if ( itv != null )
            {
                x = __smallest_abs( cooks0, cooks1, itv, y );

                double dist = Math.sqrt( x*x+y*y );
                if ( dist < d )
                {
                    d = dist;
                    ret = new Location(x,y);
                }
            }
        }

        return ret;
    }

    public static final double DELTA2 = 1E-3;
    /** remove Cookie cooks from a simple region
     */
    public static SimpleRegion[] difference(
        SimpleRegion reg,  Cookie cooks ) throws Group7Exception {

        // add all crossLocation to polygons reg.cooks and cooks
        Cookie[] cookss = reg.cooks.crossify( cooks );

        // if there is no change for both polygons
        if ( null == cookss )
        {
            // check the relationship between two original polygons
            int cmp = cooks.compare( reg.cooks );
            // if cooks is a superset of the Cookie, return null;
            if ( cmp == Cookie.CMP_SUPER )
                return null;

            // if the root Cookie of the region covers cooks, add
            // cooks to the holes
            if ( cmp == Cookie.CMP_SUB )
            {
                Cookie[] nh;
                if ( null == reg.holes || reg.holes.length == 0 )
                    nh = new Cookie[] { cooks };
                else
                {
                    nh = new Cookie[reg.holes.length+1];
                    System.arraycopy( reg.holes, 0, nh, 1, reg.holes.length );
                    nh[0] = cooks;
                }

                return new SimpleRegion[] { new SimpleRegion ( reg.cooks, nh ) };
            }

            // otherwise, the region is not affected
            return new SimpleRegion[] { reg };
        }

        // compute the cut sets of difference
        Cookie[][] cutsets = cookss[0].cutsets( cookss[1] );
        if ( cutsets == null )
            return null;
        Cookie[] cut = cutsets[1];
        if ( null == cut || cut.length == 0 )
            return null;

        // System.out.println( "Cut length: " + cut.length );

        Vector plist = new Vector();
        // for all cut sets
        for ( int i=0; i<cut.length; i++ )
        {
            // create a region contains only one of the cut Cookie
            SimpleRegion cutreg = new SimpleRegion( cut[i], null );

            // if there is no holes in the original Cookie, add
            // this region to the result
            if ( reg.holes == null || reg.holes.length == 0 )
            {
                plist.add( cutreg );
                continue;
            }

            // if there are some holes, we have to do something
            // more complicated.
            SimpleRegion[] tmp = new SimpleRegion[] { cutreg };

            // try to remove holes from the cut Cookie
            for ( int j=0; j<reg.holes.length; j++ )
            {
                tmp = difference( tmp, reg.holes[j] );
                if ( tmp == null )
                    break;
            }


            if ( tmp != null )
            {
                for ( int k=0; k<tmp.length; k++ )
                    plist.add( tmp[k] );
            }
        }

        return (SimpleRegion[]) plist.toArray( new SimpleRegion[0] );
    }

    /** remove Cookie to a region set
     */
    public static SimpleRegion[] difference(
        SimpleRegion[] reg, Cookie cooks ) throws Group7Exception {

        Vector plist = new Vector();

        for ( int i=0; i<reg.length; i++ )
        {

            SimpleRegion[] tmp = null;

            Cookie cooks1 = cooks;
            for ( int k=0; ; k++ )
            {
                try
                {
                    tmp = difference( reg[i], cooks );
                }
                catch ( Group7Exception e )
                {


                    cooks1 = cooks1.dilate( DELTA2*k*k );
                    if ( k > 10 )
                        throw e;
                    continue;

                }
                break;
            }

            if ( tmp != null )
            {
                for ( int k=0; k<tmp.length; k++ )
                    plist.add( tmp[k] );
            }
        }

        return ( SimpleRegion[] ) plist.toArray( new SimpleRegion[0] );
    }

    public static Cookie crossingOffset( Connector s0, Connector s1 ) {
        double x0 = ( s0.x0() + s0.x1() ) * 0.5;
        double y0 = ( s0.y0() + s0.y1() ) * 0.5;
        double x1 = ( s1.x0() + s1.x1() ) * 0.5;
        double y1 = ( s1.y0() + s1.y1() ) * 0.5;
        double x = x1 - x0;
        double y = y1 - y0;
        double dx0 = ( s0.x1() - s0.x0() ) * 0.5;
        double dy0 = ( s0.y1() - s0.y0() ) * 0.5;
        double dx1 = ( s1.x1() - s1.x0() ) * 0.5;
        double dy1 = ( s1.y1() - s1.y0() ) * 0.5;

        Cookie ret = new Cookie ( new Location[] {
            new Location( x + dx0 + dx1, y + dy0 + dy1 ),
            new Location( x + dx0 - dx1, y + dy0 - dy1 ),
            new Location( x - dx0 - dx1, y - dy0 - dy1 ),
            new Location( x - dx0 + dx1, y - dy0 + dy1 ) } );

        return ret.abs().dilate( DELTA2 + DELTA2 * Math.random() );
    }

    public static SimpleRegion[] offsetRegions( SimpleRegion[] prev,
                                                Cookie last, Cookie cooks ) {

        SimpleRegion[] ret = prev;
        for ( int i=0; i<last.n(); i++ )
            for ( int j=0; j<cooks.n(); j++ )
            {
                Cookie cooks1 = crossingOffset( cooks.edge(j), last.edge(i) );
                Rectangle bd1 = cooks1.bound();
                boolean flag = true;
                for ( int k=0; k<ret.length; k++ )
                    if ( bd1.intersects( ret[k].bound ) )
                    {
                        flag = false;
                        break;
                    }

                if ( flag )
                    continue;

                for ( int k=0; k<10; k++ )
                {
                    try
                    {
                        ret = difference( ret, cooks1 );
                    }
                    catch ( Group7Exception e)
                    {
                        cooks1 = cooks1.dilate( DELTA2*k );
                        continue;
                    }
                }

            }
        return ret;
    }

    public Strategy greacySolver( Cookie cooks ) {
        final int nr_angles = 12;
        Cookie[] rcookss = new Cookie[nr_angles];
        SimpleRegion[][] regsets = new SimpleRegion[nr_angles][];
        Connect ln = new Connect( new Location(0,0), new Location(-0.0001, 1) );
        Cookie[] hist = new Cookie[_nc];
        Strategy ret = new Strategy(_nc);

        Rectangle rect = new Rectangle( 0, 0, 300, _sh );

        for ( int i=0; i<nr_angles; i++ )
        {
            rcookss[i] = cooks.rotate( i*Math.PI*2/nr_angles );
            Rectangle bd = rcookss[i].bound();
            regsets[i] = new SimpleRegion[] {
                new SimpleRegion(
                    new Cookie(
                        new Rectangle(
                            rect.x0() - bd.x0(), rect.y0() - bd.y0(),
                            rect.width() - bd.width(),
                            rect.height() - bd.height() ) ),
                    null ) };

        }

        double rbound = 0;
        for ( int i=0; i<_nc; i++ )
        {
            double dist = 1E100;
            Location pos = null;
            double angle = 0;
            for ( int j=0; j<nr_angles; j++ )
            {
                Location p = closestVertex( regsets[j], ln );
                if ( p == null )
                    continue;
                if ( _min_dist < dist )
                {
                    dist = _min_dist;
                    pos = p;
                    angle = j*Math.PI*2/nr_angles;
                }
            }

            // System.out.println( "Cookie #" + i );
            ret.x[i] = pos.x();
            ret.y[i] = pos.y();
            ret.angle[i] = angle;
            hist[i] = cooks.rotate(angle).translate( ret.x[i], ret.y[i] );
            Rectangle bd1 = hist[i].bound();
            if ( bd1.x1() > ret.rbound )
                ret.rbound = bd1.x1();

            for ( int j=0; j<nr_angles; j++ )
                regsets[j] = offsetRegions( regsets[j], hist[i], rcookss[j] );
        }
        return ret;
    }

    static double _min_dist = 1E100;
    public static Location closestVertex( SimpleRegion[] regs, Connect ln ) {
        if ( regs == null || regs.length == 0 )
            return null;

        Location p = null;
        double x = 1E100;

        for ( int i=0; i<regs.length; i++ )
        {
            Location px = regs[i].cooks.closest( ln );
            double dist = ln.distance( px );
            if ( dist < x )
            {
                p = px;
                x = dist;
            }
        }

        _min_dist = x;

        return p;
    }

    public Strategy PuzzleSolver( Cookie shape1, Cookie shape2) {
	double currentminx = -1;
	int angle;
	int testnum=1;
	double xtomove;
	double ytomove;
	double x2tomove;
	double y2tomove;
	double x2finalmove=0;
	double y2finalmove=0;
	double xtoright=0;
	double ytoright=0;
	double xborder=0;
	double xwinningmove=0;
	double ywinningmove=0;
	double currentxstart=0;
	double currentystart=0;
	//double[] centroid1;
	//	Location Centroid1, Centroid2;
	int angle2final = 0;
	double xtostartwith = 0;
        //if (shape1.isConvex())
        //    return null;
        double d=1E-6;
        Cookie LargeShape1=shape1.dilate(d);
        for ( int i=0; i<360; i=i+5 )
            {
                double angleToMove = Math.toRadians( i );
                Cookie shapeRotated2 = shape2.rotate( angleToMove );

                for ( int j=0; j<LargeShape1.n(); j++) {
                    double angle1 = LargeShape1.angle(j);
                    for ( int k=0; k<shapeRotated2.n(); k++) {
                        double angle2 = shapeRotated2.angle(k);
                        if (angle1 + angle2 < 360) {
                            double x1 = LargeShape1.x(j);
                            double y1 = LargeShape1.y(j);
                            double x2 = shapeRotated2.x(k);
                            double y2 = shapeRotated2.y(k);
                            xtomove = x1-x2;
                            ytomove = y1-y2;
                            Cookie shapeTranslated2 =
                                shapeRotated2.translate(xtomove, ytomove);
                            if (!(shape1.intersects(shapeTranslated2))) {

                                Rectangle bd1, bd2, bd;
				double xdist; double ydist;

                                bd1 = shape1.bound();
                                bd2 = shapeTranslated2.bound();


                                bd = bd1.union( bd2 );

                                xdist = bd.width(); ydist = bd.height();

				if (!(ydist > 1 )) {

				    if (((testnum==1) || (xdist*ydist < currentminx)) && (xdist > 0)) {
					testnum=2;
					currentminx = xdist*ydist;
					xwinningmove=xdist+d;
					ywinningmove=ydist+d;
					xborder = Math.abs(xdist + d);
					xtoright = -bd.x0(); // -minx;
					ytoright = -bd.y0(); // -miny;
					x2finalmove = xtomove;
					y2finalmove = ytomove;
					angle2final = i;

				    }

				}
			    }
			}
		    }
		}
	    }
	if (currentminx > 0) { System.out.println( "Giving Strategy");
	    Strategy ret = new Strategy( _nc );
	    for ( int j=0; j<_nc; j++ )
                {


		    if (j%2 == 0) {
			ret.x[j] = currentxstart+xtoright;
			ret.y[j] = currentystart + ytoright;//Centroid1.y();
			ret.angle[j] = 0;

		    }
		    else {
			ret.x[j] = currentxstart+ x2finalmove + xtoright;
			ret.y[j] = currentystart + y2finalmove + ytoright;
			ret.angle[j] = Math.toRadians( angle2final );
			currentystart=currentystart+ywinningmove;
			if ((currentystart+ywinningmove)> 1+d) {
			    currentystart=0;
			    currentxstart=currentxstart+xwinningmove;
			}
			xtostartwith = xtostartwith + currentminx + d;
		    }
		}
	    ret.rbound=currentxstart+xwinningmove;
	    return ret;
	}

	else return null;
    }

}


class SimpleRegion {
    Cookie cooks;
    Cookie[] holes;
    Rectangle bound;

    SimpleRegion( Cookie p, Cookie[] h ) {
        cooks = p;
        holes = h;
        bound = p.bound();
    }

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

        sb.append( cooks.toString() );
        if ( holes != null )
        {
            for ( int i=0; i<holes.length; i++ )
            {
                sb.append( holes[i].toString() );
                sb.append( "-" );
            }
        }

        return sb.toString();
    }

    static String toString( SimpleRegion[] regs ) {
        if ( regs == null )
            return "{null}";

        StringBuffer sb = new StringBuffer();
        sb.append( "{ " );
        for ( int i=0; ; i++ )
        {
            sb.append( regs[i].toString() );
            if ( i >= regs.length - 1 )
                break;
            sb.append( ", " );
        }
        sb.append( " }" );
        return sb.toString();
    }
}
