import java.net.*;
import java.awt.image.*;
import java.applet.Applet;
import java.awt.*;
import java.lang.*;

class loadThread extends Thread {
MediaTracker tracker;
piece owner;
Image pic;
int index;
String image_name;

        public loadThread(String str,String image_name, piece obj,int index) {
                super(str);
                owner = obj;
                this.image_name = image_name;
                this.index = index;
        }

        public void run() {
                //System.out.println("Entering run of thread "+index);
                pic = owner.obj.getImage(owner.obj.getDocumentBase(),image_name);
                //owner.obj.tracker.addImage(pic,index);
                tracker = new MediaTracker(owner.obj);
                tracker.addImage(pic,index);
                try {
                         tracker.waitForID(index);
                  } catch  (InterruptedException e) {
                }
                owner.setImage(pic);
                //owner.draw();
                //System.out.println("finished "+getName());
        }
}


class piece {
        Integer value;          // the value of the piece
        Integer x_pos,y_pos;    // the on screen position of the piece
        int width=50,height=50; 
        Graphics gc;
        int right,left,up,down;
        Image pic;
        int flags;
        puzzle obj;
        loadThread img_thread;
        
        
        piece(int index,int val, Graphics agc, int x, int y,Image pict,puzzle obj) {
                int temp;

                /*if (pict != null) {
                        pic = pict;
                        obj=obj;
                } else {
                        pic = null;
                        obj=null;
                }

                if (pict != null) {
                        pict = pic;
                }
*/
                if (obj != null) {
                        this.obj = obj;
                        pic = null;
                         //pic = obj.getImage(obj.getDocumentBase(),"skippy_"+val+".gif");
                        img_thread = new loadThread("thread"+index, obj.image_name+val+".gif", this, index);
                        img_thread.start();
        //               obj.tracker.addImage(pic,index); 
                }
                value = new Integer(val);
                gc = agc;
                x_pos = new Integer(x);
                y_pos = new Integer(y);

                // RIGHT  
                temp = index+1; 
                if ((temp == 4) || ( temp == 8) || (temp ==12) || (temp==16)) {
                        right = -1;
                }else{
                        right = temp;
                } 

                // LEFT 
                temp = index-1;
                if ((temp == 3) || (temp == 7) || (temp == 11) ) {
                        left = -1;
                }else{
                        left = temp;
                }

                // UP 
                temp = index-4;
                if ((temp<0)) {
                        up = -1;
                } else {
                        up = temp;
                }

                // DOWN 
                temp = index+4;
                if ((temp >= 16)) {
                        down = -1;
                } else {
                        down = temp;
                }
// !!!          draw();
        }

        public void setImage(Image pic) {
                if (pic != null) {
                        this.pic = pic;
                }
        }

        // checks if a point is inside the piece

        public boolean point_inside (int x, int y) {

                if ((x >= x_pos.intValue()) && (y >= y_pos.intValue()) && (x <= x_pos.intValue()+width) && (y <= y_pos.intValue()+height)) {
                                return true;
                } else {
                        return false;
                }

        }

        // RETURNS TRUE IF A POINT IS IN THE SAME COL AS y
        public boolean same_col(int x) {
                return ((x > x_pos.intValue()) && (x < x_pos.intValue()+height));
        }

        // RETURNS TRUE IF A POINT IS IN THE SAME ROW AS x
        public boolean same_row(int y) {
                return ((y > y_pos.intValue()) && (y < y_pos.intValue()+width));
        }

        public boolean toLeft(piece p) {
                return (x_pos.intValue() < p.x_pos.intValue());
        } 

        public boolean toRight(piece p) {
                return (x_pos.intValue() > p.x_pos.intValue());
        } 

        public boolean above(piece p) {
                return (y_pos.intValue() < p.y_pos.intValue());
        } 

        public boolean below(piece p) {
                return (y_pos.intValue() > p.y_pos.intValue());
        } 
        public void xchg(piece blank) {
                Integer temp;
                Image temp_pic;

                temp = value;
                value = blank.value;
                blank.value = temp;


                if (pic != null ) {
                        temp_pic = pic;
                        pic = blank.pic;
                        blank.pic = temp_pic;
                }

                draw();
                blank.draw();
        }
                

        public void draw() {



                //System.out.println("In draw\n");
                if (piece.value.intValue() == 16) {
                        gc.setColor(Color.black);
                        gc.fillRect(x_pos.intValue(),y_pos.intValue(),width,height);
                        gc.drawRect(x_pos.intValue(),y_pos.intValue(),width,height);
                } else if (obj.level_indicator.level()==0) {
                        gc.setColor(Color.yellow);
                        gc.fillRect(x_pos.intValue(),y_pos.intValue(),width,height);

                        gc.setColor(Color.black);
                        gc.drawRect(x_pos.intValue(),y_pos.intValue(),width,height);
                        //System.out.println(value.toString());
                        //gc.drawString(value.toString(),(int)width/2,(int)height/2);
        //                      gc.drawString(value.toString(),x_pos.intValue()+width/2,y_pos.intValue()+height/2);
        gc.setFont(new Font("Times", Font.BOLD, 16));
                                gc.drawString(value.toString(),x_pos.intValue()+(width-gc.getFontMetrics().stringWidth(value.toString()))/2,y_pos.intValue()+height/2);
                } else  {
                        
                        gc.setColor(Color.black);
                        gc.drawRect(x_pos.intValue(),y_pos.intValue(),width,height);
                        gc.drawImage(pic,x_pos.intValue(),y_pos.intValue(),null);
                        if (obj.level_indicator.level() == 2) {
                                gc.setColor(Color.yellow);
                                gc.setFont(new Font("Times", Font.BOLD, 16));
                                gc.drawString(value.toString(),x_pos.intValue()+(width-gc.getFontMetrics().stringWidth(value.toString()))/2,y_pos.intValue()+height/2);
                        }
                } 


}
}

/* THIS PUZZLE IS 1-15 IN ORDER */
abstract class puzzle_layout  {
        protected int win[] = new int[16];
        protected int win_index, start_index;
        protected int i;
        puzzle puz;

        puzzle_layout() {
        }

                
        // RETURN THE STARTING POSITION OF THE BLANK PIECE

        public int startPos() {
                return start_index;
        }

        // RETURN THE WINNING POSITION OF THE BLANK PIECE

        public int winPos() {
                return win_index;
        }

        //      CHECKS IF THE PUZZLE IS COMPLETE

        public abstract boolean puzzle_complete(); 
        
}

class simple_puzzle extends puzzle_layout {   
        

        simple_puzzle(int start_pos,puzzle puzl)  {
                int i;

                for (i = 0; i <= (16-1); i++) {
                        win[i] = i+1;
                }
                win_index = 15;
                start_index = start_pos;
                puz  = puzl;
        }

        public boolean puzzle_complete() {
                int i;

                if (puz.getBlankLocValue() != win_index) {
                        return false;
                }
                for (i=0; i<= (16-1); i++) {
                        if (puz.getLocValue(i) != win[i]) {
                                return false;
                        }
                }
                return true;
        }

                
}
/*******************************************************************/

//      THIS CLASS IS USED TO PROVIDE A UNIQUE RANDOM VALUE FROM 
//  A RANGE OF VALUES 

class random_value {
        int value[];
        int total;


        random_value(int first, int last){
                int i;

                total = last - first +1;        

                value = new int[total];
                for (i=0; i<total; i++) {
                                value[i] = first;
                                first++;
                
                }
        }

        int getRandomValue() {
                boolean found = false;
                int i;
                int rand,ret;

                if (total == 0) { return -1; }

                if (total == 1) { total=0; return value[0]; }

                i = (int) (Math.random() * total);
                ret = value[i];

                total--;
                for (i=i;i<total;i++) {
                        value[i]=value[i+1];
                }

                return ret;
        }
}

class selector {
int level;
puzzle puz;
int width=100;
int height=30;
Integer x_pos;
Integer y_pos;

selector (int lev, puzzle obj,int x, int y) {
        level = lev;
        puz = obj;
        x_pos = new Integer(x);
        y_pos = new Integer(y);
}
        
public int level (){
        return level;
}

public void change (){
        level++;
        if (level > 2) { level = 0; }
}
public boolean inside (int x, int y) {
 return (((x >= x_pos.intValue()) && (y >= y_pos.intValue()) && (x <= x_pos.intValue()+width) && (y <= y_pos.intValue()+height)));
}
        

public void paint() {
        String Level;
        Graphics gc = puz.myG;
        
        //System.out.println("In level painter\n");
        gc.setColor(Color.black);
        gc.fillRect(x_pos.intValue(),y_pos.intValue(),width,height);
        gc.setColor(Color.yellow);

        gc.setFont(new Font("Times", Font.BOLD, 16));
        Level = "View 1";
        if (level == 1) {
                Level = "View 2";
        } else if (level == 2) {
                Level = "View 3";
        }
        gc.drawString(Level,x_pos.intValue()+(width-gc.getFontMetrics().stringWidth(Level))/2,y_pos.intValue()+height/2);

}       
}


/* THIS CLASS CHECKS WHETHER ALL IMAGES HAVE BEEN OBTAINED
   IF NOT IT RETURNS A STRING ASKING THE USER TO WAIT.
   IT EVENTUALY DISPLAYS A MESSAGE INDICATING COMPLETION OF
   THE DOWNLOAD */

class media_checker extends Thread {
Graphics myG;
puzzle puz;

        public media_checker(Graphics gc,puzzle puz) {
                super("mediachecker");
                myG = gc;
                this.puz = puz;
        }

        public void run() {

                int j;
                String str;
                boolean done = false;

                //System.out.println("In media_checker thread\n");
                while (!done) { 
                for (j=0; j<=15; j++) {
                        if (puz.pieces[j].img_thread.isAlive()) {
                                //System.out.println("waiting on "+j);
/*
                                user_waiting = true;
                                str = "All Images not loaded, try again soon...";
                                myG.setFont(new Font("Times", Font.BOLD, 8));
                                myG.setColor(Color.black);
                                myG.drawString(str,10,275);
                                
*/
                                break;
                         }
                }
                if (j > 15) { 
                        puz.images_loaded = true;
                        if (puz.user_waiting) {
                                str = "Image loading complete!!";
                                myG.setColor(Color.pink);
                                myG.fillRect(0,251,300,300);
                                myG.setFont(new Font("Times", Font.BOLD, 8));
                                myG.setColor(Color.black);
                                myG.drawString(str,10,275);
                        }
                        done = true;
                } else {
                        //System.out.print("slleping cause j was ");
                        //System.out.println(j);
                        try { 
                                sleep(1000);
                        } catch (InterruptedException e) {
                        }

                        //System.out.println("awake now");
                }
                }
        }       


}

/*******************************************************************/

public class puzzle extends Applet {
        public piece pieces[] = new piece[16];
        int i,x,y,cur_val;
        int start_x=50;
        int start_y=50;         // start pos of puzzle
        int row_size = 4;
        int piece_width = 50;
        int piece_height = 50;
        private int blank_piece_pos;
        Graphics myG;
        simple_puzzle simple;
        random_value value_supplier = new random_value(1,16);
        MediaTracker tracker;
        selector level_indicator;
        Thread loadThread; // thread used to load images
        String image_name;
        media_checker checker;
        boolean images_loaded = false;
        boolean user_waiting = false;


        public void init() {
                myG = getGraphics();
                checker = null;
                tracker = new MediaTracker(this);
                level_indicator = new selector(0,this,100, 5);
                image_name = getParameter("PREFIX");

/*      INITIALIZE THE BLANK PIECES */
                x = start_x;
                y = start_y;
                for (i=0; i <= 15; i++) {
                        cur_val = value_supplier.getRandomValue();
                        //cur_val = i+1;
/*      SET THE INITIAL POSTION OF THE BLANK PIECE */

                        if (cur_val == 16) {
                                blank_piece_pos = i;
                        }
                        //pict = getImage(getDocumentBase(),image_name+cur_val+".gif");
                        pieces[i] = new piece(i,cur_val,myG,x,y,null,this);
                        x += 50;
                        if (x > 200) { x = 50; y += 50; }
                }

/*      CREATE A NEW SIMPLE PUZZLE */
                simple = new simple_puzzle(15,this);

                resize(pieces[0].width*6,pieces[0].height*6);
                         checker = new  media_checker(myG,this);
                         checker.start();
        }

        public void start()
                {


/*
          if (loadThread == null) {
                loadThread = new Thread(this,"load");
                loadThread.start();
          }
*/

        }

        public void stop() {
                //System.out.println("stop\n");
          if (loadThread != null) {
                loadThread.stop();
                loadThread = null;
                }
        }

        public void run() {
                
        }

        public boolean mouseDown(Event evt, int x, int y) {
                int i;
                int rel_x,rel_y;
                int click_piece;
                int cur;
                String str;
        

        // FIRST CHECK IF THE POINT IS INSIDE THE PUZZLE
                        if (point_inside(x,y)) {
        // NOW GET THE CLICK PIECE
                                rel_x = (int)(x - start_x)/piece_width;
                                rel_y = (int)(y - start_y)/piece_height;
                                click_piece = rel_x + row_size * rel_y;
        // IF THE CLICK POINT IS IN THE SAME ROW AS THE BLANK PIECE

        
                        if (pieces[blank_piece_pos].same_row(y)) {
                                if (pieces[click_piece].toLeft(pieces[blank_piece_pos])) {
                                        cur = pieces[blank_piece_pos].left; 
                                        while (cur != pieces[click_piece].left) {
                                                pieces[cur].xchg(pieces[blank_piece_pos]);
                                                blank_piece_pos = cur;
                                                cur = pieces[blank_piece_pos].left;
                                                if (simple.puzzle_complete()) {
                    myG.drawString("Congratulations!!!",75,275);
                    }

                                        }
                                } else if (pieces[click_piece].toRight(pieces[blank_piece_pos])) {
                                        cur = pieces[blank_piece_pos].right; 
                                        while (cur != pieces[click_piece].right) {
                                                pieces[cur].xchg(pieces[blank_piece_pos]);
                                                blank_piece_pos = cur;
                                                cur = pieces[blank_piece_pos].right;
                                                if (simple.puzzle_complete()) {
                    myG.drawString("Congratulations!!!",75,275);
                    }

                                        }
                                } 
                        } else if (pieces[blank_piece_pos].same_col(x)) {
                if (pieces[click_piece].above(pieces[blank_piece_pos])) {
                    cur = pieces[blank_piece_pos].up;        
                    while (cur != pieces[click_piece].up) {
                        pieces[cur].xchg(pieces[blank_piece_pos]);
                        blank_piece_pos = cur;
                        cur = pieces[blank_piece_pos].up;
                                                if (simple.puzzle_complete()) {
                    myG.drawString("Congratulations!!!",75,275);
                    }

                    }
                } else if (pieces[click_piece].below(pieces[blank_piece_pos])) {
                    cur = pieces[blank_piece_pos].down;
                    while (cur != pieces[click_piece].down) {
                        pieces[cur].xchg(pieces[blank_piece_pos]);
                        blank_piece_pos = cur;
                        cur = pieces[blank_piece_pos].down;
                                                if (simple.puzzle_complete()) {
                    myG.drawString("Congratulations!!!",75,275);
                    }

                    }
                } 
                        }
        } else if (level_indicator.inside(x,y)) {
                if (images_loaded == true) {

                        if (user_waiting == true) {
                                str = "Image loading complete!!";
                                myG.setColor(Color.pink);
                                myG.fillRect(0,251,300,300);
                                myG.setFont(new Font("Times", Font.BOLD, 8));
                                myG.setColor(Color.black);
                                myG.drawString(str,10,275);
                                user_waiting = false;
                        } 

                        level_indicator.change();
                        level_indicator.paint();
                        paint(myG);     
                } else {
                                user_waiting = true;
                                str = "All Images not loaded, try again soon...";
                                myG.setFont(new Font("Times", Font.BOLD, 8));
                                myG.setColor(Color.black);
                                myG.drawString(str,10,275);
                }

        }
                return true;
        }


        public void paint(Graphics g) {
                int i;
                int j;
                int cur;

                //System.out.println("In main paint\n");
                g.setColor(Color.pink);
                g.fillRect(0,0,size().width,size().height);

        //      System.out.println("HERE");
                cur = 0;
                for (i=0; i <= (15); i++) {
                        pieces[i].draw();
                }
                level_indicator.paint();
                        
                
        }

/*
        public void update(Graphics g) {
        for (i=0; i <= (15); i++) {
                        pieces[i].draw();
                }
        }
*/

        //      THIS ROUTINE RETURNS THE VALUE OF A LOCATION
        int getLocValue(int arr_loc) {
        
                return pieces[arr_loc].value.intValue();
        }

        //  THIS ROUTINE RETURNS THE VALUE OF THE BLANK LOCATION
        int getBlankLocValue() {
                return pieces[blank_piece_pos].value.intValue();
        }                       

        public boolean point_inside (int x, int y) {

                if ((x >= start_x) && (y >= start_y) && (x <= start_x+row_size*piece_width) && (y <= start_y+row_size*piece_width)) {
                                return true;
                } else {
                        return false;
                }

        }
                
}

        

