//
// The Rubix cube program 
// Copyright c Brian P. Vogl
// September 15 - 16, 2000
//
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class rubix extends Applet implements ActionListener
{	StringBuffer undostack = new StringBuffer();
	int autostop = 0, undostop = 0;
	long waittime = 500;
	int[][] CD = {  //new int[6][9]; Color of each square
		{ 1, 1, 1, 1, 1, 1, 1, 1, 1 },	// Green face
		{ 2, 2, 2, 2, 2, 2, 2, 2, 2 },	// Yellow face
		{ 3, 3, 3, 3, 3, 3, 3, 3, 3 },	// Red face
		{ 4, 4, 4, 4, 4, 4, 4, 4, 4 },	// Blue face
		{ 5, 5, 5, 5, 5, 5, 5, 5, 5 },	// White face
		{ 6, 6, 6, 6, 6, 6, 6, 6, 6 } };	// Orange face
	int[][][] AP = {	//new int[12][6][2];  Rotation Rules
		{{4,6}, {3,6}, {3,0}, {4,2}, {3,2}, {3,8}},	
		{{4,7}, {3,7}, {3,3}, {4,1}, {3,1}, {3,5}},
		{{4,8}, {3,8}, {3,6}, {4,0}, {3,0}, {3,2}},
		{{2,6}, {5,0}, {1,0}, {5,2}, {2,0}, {4,0}},
		{{2,7}, {5,3}, {1,3}, {5,1}, {2,3}, {4,3}},
		{{2,8}, {5,6}, {1,6}, {5,0}, {2,6}, {4,6}},
		{{1,6}, {0,6}, {0,8}, {1,2}, {0,2}, {0,0}},
		{{1,7}, {0,7}, {0,5}, {1,1}, {0,1}, {0,3}},
		{{1,8}, {0,8}, {0,2}, {1,0}, {0,0}, {0,6}},
		{{5,6}, {2,8}, {4,8}, {2,2}, {5,8}, {1,8}},
		{{5,7}, {2,5}, {4,5}, {2,1}, {5,5}, {1,5}},
		{{5,8}, {2,2}, {4,2}, {2,0}, {5,2}, {1,2}}	};
	// Polygon vertices, (X,Y) polygon number (0-2) and index into CD array.
	int[][] QP = {  //new int[5][54];
		{139,473,475, 93,185,427,521, 47,139,231,381,473,475,567,  2, 93,185,276,	// X
		 427,521, 48,139,230,381,473,475,567,  2, 94,184,276,427,474,521, 48,230,
		 381,428,520,567,  2, 94,184,276,382,474,566, 48,230,428,520, 94,184,474 },
		{ -8,-10,-10,  8,  8,  6,  6, 24, 24, 24, 22, 20, 20, 22, 40, 40, 40, 40,	// Y
		  36, 36, 56, 56, 56, 52, 50, 50, 52, 70, 72, 72, 70, 66, 80, 66, 86, 86,
		  82, 96, 96, 82,100,102,102,100,112,112,112,116,116,128,128,132,132,144 },
		{0,2,1,0,0,2,1,0,0,0,2,2,1,1,1,0,0,2,2,1,1,0,2,2,2,1,1,		// Polygon type
		 1,1,2,2,2,0,1,1,2,2,0,0,1,1,1,2,2,0,0,0,1,2,0,0,1,2,0  },
		{0,5,4,0,0,5,4,0,0,0,5,5,4,4,1,0,0,2,5,4,1,0,2,5,5,4,4,		// X index into CD
		 1,1,2,2,5,3,4,1,2,5,3,3,4,1,1,2,2,3,3,3,1,2,3,3,1,2,3  },
		{0,8,6,3,1,7,7,6,4,2,6,5,3,8,8,7,5,6,4,4,7,8,7,3,2,0,5,		// Y index into CD
		 5,6,8,3,1,2,1,4,4,0,5,1,2,2,3,5,0,8,4,0,1,1,7,3,0,2,6  }  };
	int[][] RP = {  //new int[4][42];  Points (X1,Y1)-(X2,Y2) lines
		{  1,  1,139,277,277,139,  1,139,231, 93,185, 47, 93,231,	// X1
		  47,185,139,  1,139,  1,139,336,474,612,612,474,336,336,
		 474,566,428,520,382,428,566,382,520,474,336,474,336,474 },
		{ 39,129,177,129, 39, -9, 39, 87, 23, 71,  7, 55,  7, 55,	// Y1
		  23, 71, 87, 69,117, 99,147,127,175,127, 37,-11, 37,127,
		  79,143, 95,159,111,159,111,143, 95, 79, 67, 19, 97, 49 },
		{  1,139,277,277,139,  1,139,277, 93, 93, 47, 47,231,231,	// X2
		 185,185,139,139,277,139,277,474,612,612,474,336,336,474,
		 612,428,428,382,382,566,566,520,520,474,474,612,474,612 },
		{129,177,129, 39, -9, 39, 87, 39, 71,161, 55,145, 55,145,	// Y2
		  71,161,177,117, 69,147, 99,175,127, 37,-11, 37,127, 79,
		 127, 95,  5,111, 21,111, 21, 95,  5,-11, 19, 67, 49, 97 } };
	int[] PP = { 0,2,8,6,1,5,7,3 };

	Color black = new Color(0x00,0x00,0x00);
	Color green = new Color(0xC6,0xEF,0x8C);
	Color yellow = new Color(0xFF,0xFF,0x9C);
	Color red = new Color(0xFF,0x63,0x42);
	Color blue = new Color(0x63,0xC6,0xDE);
	Color white = new Color(0xFF,0xFF,0xFF);
	Color orange = new Color(0xFF,0xB5,0x73);
//////Misc buttons
	Button auto = new Button("Auto");
	Button undo = new Button("Undo");

//////////////////ELIMINATED THE BUTTONS WE NOW CLICK SIDES OF THE CUBE ///////////////////
//////Up buttons
//	Button greenup = new Button("Green");
//	Button yellowup = new Button("Yellow");
//	Button redup = new Button("Red");
//	Button blueup = new Button("Blue");
//	Button whiteup = new Button("White");
//	Button orangeup = new Button("Orange");
//////Down buttons
//	Button greendn = new Button("Green");
//	Button yellowdn = new Button("Yellow");
//	Button reddn = new Button("Red");
//	Button bluedn = new Button("Blue");
//	Button whitedn = new Button("White");
//	Button orangedn = new Button("Orange");
//				
//	public class controlAreaUP extends Panel
//	{	public controlAreaUP()
//		{
//			greenup.setBackground(green);
//			yellowup.setBackground(yellow);
//			redup.setBackground(red);
//			blueup.setBackground(blue);
//			whiteup.setBackground(white);
//			orangeup.setBackground(orange);
//			greenup.setActionCommand("GU");
//			yellowup.setActionCommand("YU");
//			redup.setActionCommand("RU");
//			blueup.setActionCommand("BU");
//			whiteup.setActionCommand("WU");
//			orangeup.setActionCommand("OU");
//			setLayout(new FlowLayout(FlowLayout.CENTER));
//			add(greenup);
//			add(yellowup);
//			add(redup);
//			add(blueup);
//			add(whiteup);
//			add(orangeup);
//		}
//	}
//
//	public class controlAreaDN extends Panel
//	{	public controlAreaDN()
//		{
//			greendn.setBackground(green);
//			yellowdn.setBackground(yellow);
//			reddn.setBackground(red);
//			bluedn.setBackground(blue);
//			whitedn.setBackground(white);
//			orangedn.setBackground(orange);
//			greendn.setActionCommand("GD");
//			yellowdn.setActionCommand("YD");
//			reddn.setActionCommand("RD");
//			bluedn.setActionCommand("BD");
//			whitedn.setActionCommand("WD");
//			orangedn.setActionCommand("OD");
//			setLayout(new FlowLayout(FlowLayout.CENTER));
//			add(greendn);
//			add(yellowdn);
//			add(reddn);
//			add(bluedn);
//			add(whitedn);
//			add(orangedn);
//		}
//	}
//
//	public class controlArea extends Panel
//	{	public controlArea()
//		{	setLayout(new GridLayout(2,1));
//			controlAreaUP CUP = new controlAreaUP();
//			controlAreaDN CDN = new controlAreaDN();
//			add(CUP);
//			add(CDN);
//		}
//	}
///////////////////// OBSOLETE USING SLEEP ////////////////////////////////
//	public void waitasec()
//	{	long prevsec, nowsec;
//		Calendar dt;
//		dt = Calendar.getInstance();
//		prevsec = dt.get(dt.SECOND);
//		do
//		{	dt = Calendar.getInstance();
//			nowsec = dt.get(dt.SECOND);
//		}
//		while(nowsec == prevsec);
//	}
/////////////////////////////////////////////////////////////////////////////

	Label title = new Label("The RUBIX Cube");

	public class Line1 extends Panel
	{	Font bigFont =new Font("TimesRoman", Font.BOLD, 22);
		public Line1()
		{	setLayout(new FlowLayout(FlowLayout.CENTER));
			title.setFont(bigFont);
			add(title);
		}
	}

	public class drawArea extends Panel
	{	public drawArea()
		{
		}
	}

	public class buttonArea extends Panel
	{	public buttonArea()
		{	auto.setBackground(red);
			auto.setActionCommand("AUTO");
			undo.setBackground(blue);
			undo.setActionCommand("UNDO");
//			controlArea cent = new controlArea();

			setLayout(new FlowLayout());
			add(auto);
//			add(cent);
			add(undo);
		}
	}

	public void init()
	{	addMouseListener(new MPListener());

		Line1 NP = new Line1();
		drawArea CP = new drawArea();
		buttonArea SP = new buttonArea();
		auto.addActionListener(this);
		undo.addActionListener(this);

//////////////////////////////////////////////////////////
//		greenup.addActionListener(this);
//		yellowup.addActionListener(this);
//		redup.addActionListener(this);
//		blueup.addActionListener(this);
//		whiteup.addActionListener(this);
//		orangeup.addActionListener(this);
//		greendn.addActionListener(this);
//		yellowdn.addActionListener(this);
//		reddn.addActionListener(this);
//		bluedn.addActionListener(this);
//		whitedn.addActionListener(this);
//		orangedn.addActionListener(this);		
////////////////////////////////////////////////////////////

		setLayout(new BorderLayout());
		add(NP, "North");
		add(CP, "Center");
		add(SP, "South");
		
		Graphics gr = CP.getGraphics();
		CP.setVisible(false);
	}

	public void paint(Graphics gr)	// Draw the cube
	{	int k, Xo, Yo;
		int polyX[] = new int[5];
		int polyY[] = new int[5];

		for(k=0; k < 42; k++)		// Draw the cube grid first
		{	gr.setColor(Color.black);
			gr.drawLine(RP[0][k]+20, (RP[1][k]+35)*2, RP[2][k]+20, (RP[3][k]+35)*2);
		}
		for(k=0; k<54; k++)
		{	
			Xo = QP[0][k]+20;		// Determine the offset of the polygons
			Yo = QP[1][k]+35;
			polyX[0] = Xo;	polyY[0] = 2*Yo; 
			polyX[4] = Xo; 	polyY[4] = 2*Yo;
			switch(QP[2][k])
			{				// Select the polygon to draw
			case 0:	polyX[1] = Xo-45;	polyY[1] = 2*(Yo+15);
					polyX[2] = Xo; 	polyY[2] = 2*(Yo+30);
					polyX[3] = Xo+45;	polyY[3] = 2*(Yo+15);	break;
			case 1:	polyX[1] = Xo;	polyY[1] = 2*(Yo+28);
					polyX[2] = Xo+44;	polyY[2] = 2*(Yo+44);
					polyX[3] = Xo+44;	polyY[3] = 2*(Yo+16);	break;
			case 2:	polyX[1] = Xo-44;	polyY[1] = 2*(Yo+16);
					polyX[2] = Xo-44;	polyY[2] = 2*(Yo+44);
					polyX[3] = Xo;	polyY[3] = 2*(Yo+28);	break;
			}
			switch(CD[QP[3][k]][QP[4][k]])
			{	// Select the color to appear on the face of the polygon
			case 1 :	gr.setColor(Color.green);	break;
			case 2 :	gr.setColor(Color.yellow);	break;
			case 3 :	gr.setColor(Color.red);		break;
			case 4 :	gr.setColor(Color.blue);	break;
			case 5 :	gr.setColor(Color.white);	break;
			case 6 :	gr.setColor(Color.orange);	break;
			}	// Finally draw the polygon.
			gr.fillPolygon(polyX, polyY, polyX.length);
		}
		showStatus(Integer.toString(undostack.length()) + "  Rotations.");
	}

	class MMListener extends MouseMotionAdapter
	{	public void mouseDragged(MouseEvent e)
		{}
		public void mouseMoved(MouseEvent e)
		{	int xs=e.getX();
			int ys=e.getY();
		 	showStatus("(" + Integer.toString(xs) + ", " + Integer.toString(ys) + ")");
		}
	}

	class MPListener extends MouseAdapter
	{	public void mousePressed(MouseEvent e)
		{	// Determine which face and which button was pressed over it
			int xs=e.getX()-20;
			int ys=e.getY()/2-35;
			int fc = -1;
			if(incenter( 6, 5, 7, 4, xs, ys)) fc = 0; // GREEN Face
			if(incenter( 1, 6,16, 0, xs, ys)) fc = 1; // YELLOW Face
			if(incenter( 2, 7, 3,16, xs, ys)) fc = 2; // RED Face
			if(incenter(21,27,22,28, xs, ys)) fc = 3; // BLUE Face
			if(incenter(28,24,23,37, xs, ys)) fc = 4; // WHITE Face
			if(incenter(27,25,37,26, xs, ys)) fc = 5; // ORANGE Face
			int buttstate = e.getModifiers();

			if(fc >= 0)
			{	undostop = 0;	// stop undoing, were going to add a new one.
				autostop = 0;	// stop autoing, were clicking on faces.
				if(buttstate == e.BUTTON1_MASK && fc >= 0)
				{	undostack.append((char)(fc + 6));
					rotateDn(fc);
				}
				if(buttstate == e.BUTTON3_MASK && fc >= 0)
				{	undostack.append((char)(fc + 0));
					rotateUp(fc);
				}
				check4done();
				repaint();	// Finally Redraw the cube.
			}
		}
		public void mouseReleased(MouseEvent e)
		{	
		}
	}

	boolean incenter(int rp1, int rp2, int rp3, int rp4, int xc, int yc)
	{	// Returns true if point is in the center of the box bounded by the 4 RP lines.
		return( leftofline(rp1, xc, yc) && !leftofline(rp2, xc, yc) &&
			  leftofline(rp3, xc, yc) && !leftofline(rp4, xc, yc));
	}
	
	boolean leftofline(int rp, int xc, int yc)
	{	// Returns true if point is left of or below the RP line
		if(RP[2][rp] == RP[0][rp]) return ( xc < RP[0][rp] );
		double m = ((double)(RP[3][rp] - RP[1][rp]) / (double)(RP[2][rp] - RP[0][rp]));
		int b = RP[1][rp] - (int)(RP[0][rp] * m);
		return ( yc < ( m * xc + b ));
	}

	public void actionPerformed(ActionEvent Event)
	{	// Determine which button has been pressed
		String buton = new String(Event.getActionCommand());
		int fc = 0;

		switch(buton.charAt(0))
		{
		case 'U': undoall(); return;
		case 'A': automode(); return;
		case 'G': fc = 0; break;
		case 'Y': fc = 1; break;
		case 'R': fc = 2; break;
		case 'B': fc = 3; break;
		case 'W': fc = 4; break;
		case 'O': fc = 5; break;
		}
		undostop = 0;	// stop undoing, were going to add a new one.
		autostop = 0;	// stop autoing, were clicking on buttons.
		switch(buton.charAt(1))
		{
		case 'U':	undostack.append((char)(fc + 0));
				rotateUp(fc);	break;
		case 'D':	undostack.append((char)(fc + 6));
				rotateDn(fc);	break;
		}
		check4done();
		repaint();	// Finally Redraw the cube.
	}

	public void automode()  // Use a thread to randomly rotate the cube.
	{	if(autostop > 0) autostop = 0;
		else
		{	undostop = 0;
			autostop++;
			autothread at = new autothread();
			at.start();
		}
	}

	public void undoall()	// Use a thread to undo all previous rotations
	{	if(undostop > 0) undostop = 0;
		else
		{	autostop = 0;
			undostop++;
			undothread ut = new undothread();
			ut.start();		
		}
	}

	class autothread extends Thread
	{	public void run()
		{	int rfc;
			double rn;
			Random ran = new Random();
			try {	sleep(waittime); }
			catch( InterruptedException e) {}
			while(autostop > 0)
			{	rn = ran.nextDouble();
				if(rn < 1.0)
				{	rfc = (int) (rn * 12);
					if(rfc < 6)
					{	undostack.append((char)(rfc));
						rotateUp(rfc - 0);
					}
					else
					{	undostack.append((char)(rfc));
						rotateDn(rfc - 6);
					}
					check4done();
				}
				repaint();
				try {	sleep(waittime); }
				catch( InterruptedException e) {}
			}
		}
	}	

	class undothread extends Thread
	{	public void run()
		{	int t;
			try {	sleep(waittime); }
			catch( InterruptedException e) {}
			while((t = undostack.length()) > 0 && undostop > 0)
			{	if(undostack.charAt(t-1) < 6)
					rotateDn(undostack.charAt(t-1) - 0);
				else
					rotateUp(undostack.charAt(t-1) - 6);
				repaint();
				undostack.setLength(t-1);
				check4done();
				try {	sleep(waittime); }
				catch( InterruptedException e) {}
			}
		}
	}

	public void rotateUp(int fc)		// An upper button was desired
	{	int[] TCD = new int[12];
		int n, t, p, tmp;
		for(n=0; n<=1; n++)  // Translate the face of the cube
		{	tmp = CD[fc][PP[n*4]];
			for(t=0; t<=2; t++) CD[fc][PP[n*4+t]] = CD[fc][PP[n*4+t+1]];
			CD[fc][PP[n*4+3]] = tmp;
		}	// Translate the sides of the cube
		for(p=0; p<12; p++) TCD[p] = CD[AP[p][fc][0]][AP[p][fc][1]];
		for(n=0; n<=2; n++)
		{	tmp = TCD[n];
			for(t=0; t<=2; t++) TCD[n+t*3] = TCD[n+(t+1)*3];
			TCD[n+9] = tmp;
		} for(p=0; p<12; p++) CD[AP[p][fc][0]][AP[p][fc][1]] = TCD[p];
	}

	public void rotateDn(int fc)		// A lower button was desired 
	{	int[] TCD = new int[12];
		int n, t, p, tmp;
		for(n=1; n>=0; n--)  // Translate the face of the cube
		{	tmp = CD[fc][PP[n*4+3]];
			for(t=2; t>=0; t--) CD[fc][PP[n*4+t+1]] = CD[fc][PP[n*4+t]];
			CD[fc][PP[n*4]] = tmp;
		}	// Translate the sides of the cube
		for(p=0; p<12; p++) TCD[p] = CD[AP[p][fc][0]][AP[p][fc][1]];
		for(n=2; n>=0; n--)
		{	tmp = TCD[n+9];
			for(t=2; t>=0; t--) TCD[n+(t+1)*3] = TCD[n+t*3];
			TCD[n] = tmp;
		} for(p=0; p<12; p++) CD[AP[p][fc][0]][AP[p][fc][1]] = TCD[p];
	}

	public void check4done()
	{	int t,k;
		for(t=0; t<6; t++)
		{	for(k=0; k<9; k++)
			{	if(CD[t][k] != (t+1))
				{	return;
				}
			}
		} 
		undostack.setLength(0);
	}
}