import java.applet.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class Tetris extends Applet implements Runnable, KeyListener
{
	private Piece curPiece;
	private Spot[][] spots;
	private int level, score, lines, pieces, next;
	private int[] numnumlines;
	private Random rand;
	private boolean paused,over,swapped,drawVert;
	private long wakeTime,totalTime,prevTime;
	private Image dbImage;
	private Graphics dbg;
	public static final int FIELD_WIDTH = 10, FIELD_HEIGHT = 20;
	public static final Dimension DIM = new Dimension(FIELD_WIDTH*20+100, FIELD_HEIGHT*20+1);
	public static final int[] stable = new int[] {0,1,3,6,10};
	public static final Color L_COLOR = new Color(100,200,200);
	public static final Color S_COLOR = new Color(200,100,100);
	public static final Color Z_COLOR = new Color(200,100,200);
	public static final Color T_COLOR = new Color(200,200,100);
	public static final Color I_COLOR = new Color(100,100,200);
	public static final Color R_COLOR = new Color(100,200,100);
	public static final Color O_COLOR = new Color(100,100,100);
	public static final Color GRAY = new Color(24,24,24);
	public static final Color[] colarray = new Color[] {L_COLOR,R_COLOR,S_COLOR,Z_COLOR,T_COLOR,I_COLOR,O_COLOR};
	private boolean[][] piecearr;

	public void keyPressed(KeyEvent e)
	{
		int key = e.getKeyCode();
		switch(key)
		{
		case KeyEvent.VK_UP: if(!over && !paused) curPiece.rotate(); break;
		case KeyEvent.VK_RIGHT: if(!righted() && !over && !paused) curPiece.right(); break;
		case KeyEvent.VK_LEFT: if(!lefted() && !over && !paused) curPiece.left(); break;
		case KeyEvent.VK_SPACE:
			if(!over && !paused) {
				if(!swapped)
				{
					down(); 
					wakeTime = System.currentTimeMillis() + (3000 / (level + 2));
				}
				else
				{
					if(!isDown()) curPiece.downOne();
					else newPiece();
					wakeTime = System.currentTimeMillis() + (3000 / (level + 2));
				}
			}
			break;
		case KeyEvent.VK_DOWN:
			if(!over && !paused && !isDown()) {
				if(!swapped)
				{
					if(!isDown()) curPiece.downOne();
					else newPiece();
					wakeTime = System.currentTimeMillis() + (3000 / (level + 2));
				}
				else
				{
					down(); 
					wakeTime = System.currentTimeMillis() + (3000 / (level + 2));
				}
				
			}
			break;
		case KeyEvent.VK_N: newGame(); break;
		case KeyEvent.VK_ENTER:
			if(!over) {
				if(paused) prevTime = System.currentTimeMillis();
				paused = !paused;
			}
			repaint();
			break;
		case KeyEvent.VK_L: level++; break;
		case KeyEvent.VK_S: swapped = !swapped; break;
		case KeyEvent.VK_V: drawVert = !drawVert; repaint(); break;
		}
		repaint();
	}

	public void keyReleased(KeyEvent e) {}

	public void keyTyped(KeyEvent e) {}

	public void paint(Graphics g)
	{
		resize(DIM);
		Graphics2D g2 = (Graphics2D)g;
		g2.setColor(Color.black);
		g2.fillRect(0,0,20*FIELD_WIDTH+1,20*FIELD_HEIGHT+1);
		g2.setColor(GRAY);
		g2.fillRect(20*FIELD_WIDTH+1,0,99,20*FIELD_HEIGHT+1);
		if(!paused)
		{
			for(int i = 0; i<FIELD_WIDTH; i++)
			{
				g2.setColor(GRAY);
				if(drawVert) g2.drawLine(i*20, 0, i*20, 20*FIELD_HEIGHT+1);
				for(int j = 0; j<FIELD_HEIGHT; j++)

				{
					if(spots[i][j].getSquare() != null)
					{
						g2.setColor(spots[i][j].getSquare().getColor());
						g2.fillRect(i*20,j*20,20,20);
						g2.setColor(Color.black);
						g2.drawRect(i*20,j*20,20,20);
					}
				}
			}
		}
		g2.setColor(Color.white);
		if (paused) g2.drawString("PAUSED",20*FIELD_WIDTH+10,370);
		if(over)
			drawOver(g2);
		g2.drawString("Next",20*FIELD_WIDTH+20,20);
		g2.drawString("Score: "+score,20*FIELD_WIDTH+10,150);
		g2.drawString("Lines: "+lines,20*FIELD_WIDTH+10,170);
		g2.drawString("Pieces: "+pieces,20*FIELD_WIDTH+10,190);
		g2.drawString("Level: "+level,20*FIELD_WIDTH+10,210);
		g2.drawString("BPM: "+((double)pieces)/totalTime*1000*60,20*FIELD_WIDTH+10,230);
		for(int i = 1; i<5; i++)
			g2.drawString((i)+"-liners: "+numnumlines[i],20*FIELD_WIDTH+10,240+i*20);
		for(int i = 0; i<8; i++)
			if(piecearr[i][next])
			{
				g2.setColor(colarray[next]);
				g2.fillRect(i/4*20+20*FIELD_WIDTH+20,i%4*20+50,20,20);
				g2.setColor(Color.black);
				g2.drawRect(i/4*20+20*FIELD_WIDTH+20,i%4*20+50,20,20);
			}
	}
	public void init()
	{
		newGame();
		swapped = false;
		piecearr = new boolean[][]{{true , false, true , false, true , true , true },
				{true , false, true , true , true , true , true },
				{true , true , false, true , true , true , false},
				{false, false, false, false, false, true , false},
				{false, true , false, true , false, false, true },
				{false, true , true , true , true , false, true },
				{true , true , true , false, false, false, false},
				{false, false, false, false, false, false, false}};
		addKeyListener(this);
	}
	public void newGame()
	{
		level = 1;
		lines = score = pieces = 0;
		totalTime = 1;
		prevTime = System.currentTimeMillis();
		paused = over = false;
		numnumlines = new int[] {0,0,0,0,0};
		spots = new Spot[FIELD_WIDTH][FIELD_HEIGHT];
		for(int i = 0; i<FIELD_WIDTH; i++)
			for(int j = 0; j<FIELD_HEIGHT; j++)
				spots[i][j] = new Spot(i,j, null);
		rand = new Random();
		next = rand.nextInt(7);
		newPiece();
	}
	public void start()
	{
		Thread t = new Thread(this);
		t.start();
	}
	public void run()
	{
		while(true)
		{
			wakeTime = System.currentTimeMillis() + (3000 / (level + 2));
			while ((System.currentTimeMillis()) < wakeTime)
				try {
					Thread.sleep(wakeTime - System.currentTimeMillis());
				} catch(InterruptedException anus) {
				}
			if(!isDown() && !paused) curPiece.downOne();
			else if(!over && !paused) newPiece();
			if (!over) repaint();
		}
	}

	public void update (Graphics g)
	{
		if (dbImage == null)
		{
			dbImage = createImage (this.getSize().width, this.getSize().height);
			dbg = dbImage.getGraphics ();
		}
		dbg.setColor (getBackground ());
		dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
		dbg.setColor (getForeground());
		paint (dbg);
		g.drawImage (dbImage, 0, 0, this);
	}

	public void newPiece()
	{
		int linesAtOnce = 0;
		for(int i = FIELD_HEIGHT-1; i>=0; i--)
		{
			if(rowFull(i))
			{
				removeRow(i);
				lines++;
				linesAtOnce++;
				i++;
			}
		}
		pieces++;
		long cur = System.currentTimeMillis();
		totalTime += cur - prevTime;
		prevTime = cur;
		score += (linesAtOnce*stable[linesAtOnce]*10*level*level);
		numnumlines[linesAtOnce]++;
		level = ((lines/10+1)>level)?lines/10+1:level;
		switch(next)
		{
		case 0: curPiece = new LPiece(spots); break;
		case 1: curPiece = new RPiece(spots); break;
		case 2: curPiece = new SPiece(spots); break;
		case 3: curPiece = new ZPiece(spots); break;
		case 4: curPiece = new TPiece(spots); break;
		case 5: curPiece = new IPiece(spots); break;
		case 6: curPiece = new OPiece(spots); break;
		}
		if(isDown())
		{
			over = true;
			repaint();
		}
		next = rand.nextInt(7);
	}

	public boolean isDown()
	{
		boolean f = false;
		Spot[] s = curPiece.getSpots();
		for(int i = 0; i<4; i++)
		{
			if(s[i].getY() > FIELD_HEIGHT-2)
				return true;
		}
		for(int i = 0; i<4; i++)
		{
			Spot l = spots[s[i].getX()][s[i].getY()+1];
			if(l.getSquare() != null)
			{
				f = true;
				for(int j = 0; j<4; j++)
				{
					if(l == s[j])
						f = false;
				}
			}
			if(f) return f;
		}
		return f;
	}

	public boolean lefted()
	{
		boolean f = false;
		Spot[] s = curPiece.getSpots();
		for(int i = 0; i<4; i++)
		{
			if(s[i].getX() < 1)
				return true;
		}
		for(int i = 0; i<4; i++)
		{
			Spot l = spots[s[i].getX()-1][s[i].getY()];
			if(l.getSquare() != null)
			{
				f = true;
				for(int j = 0; j<4; j++)
				{
					if(l == s[j])
						f = false;
				}
			}
			if(f) return f;
		}
		return f;
	}

	public boolean righted()
	{
		boolean f = false;
		Spot[] s = curPiece.getSpots();
		for(int i = 0; i<4; i++)
		{
			if(s[i].getX() > FIELD_WIDTH-2)
				return true;
		}
		for(int i = 0; i<4; i++)
		{
			Spot l = spots[s[i].getX()+1][s[i].getY()];
			if(l.getSquare() != null)
			{
				f = true;
				for(int j = 0; j<4; j++)
				{
					if(l == s[j])
						f = false;
				}
			}
			if(f) return f;
		}
		return f;
	}

	public void down()
	{
		while(!isDown())
		{
			curPiece.downOne();
			score += level;
		}
		newPiece();
	}

	public boolean rowFull(int row)
	{
		for(int i = 0; i<FIELD_WIDTH; i++)
			if (spots[i][row].getSquare() == null) return false;
		return true;
	}

	public void removeRow(int row)
	{
		for(int i = row; i>0; i--)
			for(int j = 0; j<FIELD_WIDTH; j++)
				spots[j][i].setSquare(spots[j][i-1].getSquare());
		for(int i = 0; i < FIELD_WIDTH; i++)
			spots[i][0].setSquare(null);			
	}

	public void drawOver(Graphics2D g2)
	{
		int indexx = (FIELD_WIDTH*20-186)/2;
		int indexy = 100;
		int scale = 8;
		g2.setColor(Color.red);
		// G
		g2.fillRect(indexx+0,indexy+0,scale*5,scale*1);
		g2.fillRect(indexx+0,indexy+0,scale*1,scale*5);
		g2.fillRect(indexx+0,indexy+scale*4,scale*5,scale*1);
		g2.fillRect(indexx+scale*2,indexy+scale*2,scale*3,scale*1);
		g2.fillRect(indexx+scale*4,indexy+scale*2,scale*1,scale*3);
		// A
		g2.fillRect(indexx+scale*6,indexy+scale*0,scale*5,scale*1);
		g2.fillRect(indexx+scale*6,indexy+scale*0,scale*1,scale*5);
		g2.fillRect(indexx+scale*10,indexy+scale*0,scale*1,scale*5);
		g2.fillRect(indexx+scale*6,indexy+scale*2,scale*5,scale*1);
		// M
		g2.fillRect(indexx+scale*12,indexy+scale*0,scale*5,scale*1);
		g2.fillRect(indexx+scale*12,indexy+scale*0,scale*1,scale*5);
		g2.fillRect(indexx+scale*16,indexy+scale*0,scale*1,scale*5);
		g2.fillRect(indexx+scale*14,indexy+scale*0,scale*1,scale*5);
		// E
		g2.fillRect(indexx+scale*18,indexy+scale*0,scale*5,scale*1);
		g2.fillRect(indexx+scale*18,indexy+scale*0,scale*1,scale*5);
		g2.fillRect(indexx+scale*18,indexy+scale*4,scale*5,scale*1);
		g2.fillRect(indexx+scale*18,indexy+scale*2,scale*5,scale*1);
		// O
		g2.fillRect(indexx+scale*0,indexy+scale*6,scale*5,scale*1);
		g2.fillRect(indexx+scale*0,indexy+scale*6,scale*1,scale*5);
		g2.fillRect(indexx+scale*4,indexy+scale*6,scale*1,scale*5);
		g2.fillRect(indexx+scale*0,indexy+scale*10,scale*5,scale*1);
		// V
		g2.fillRect(indexx+scale*6,indexy+scale*6,scale*1,scale*2);
		g2.fillRect(indexx+scale*7,indexy+scale*8,scale*1,scale*2);
		g2.fillRect(indexx+scale*8,indexy+scale*10,scale*1,scale*1);
		g2.fillRect(indexx+scale*9,indexy+scale*8,scale*1,scale*2);
		g2.fillRect(indexx+scale*10,indexy+scale*6,scale*1,scale*2);
		// E (again)
		g2.fillRect(indexx+scale*12,indexy+scale*6,scale*5,scale*1);
		g2.fillRect(indexx+scale*12,indexy+scale*6,scale*1,scale*5);
		g2.fillRect(indexx+scale*12,indexy+scale*10,scale*5,scale*1);
		g2.fillRect(indexx+scale*12,indexy+scale*8,scale*5,scale*1);
		// R
		g2.fillRect(indexx+scale*18,indexy+scale*6,scale*5,scale*1);
		g2.fillRect(indexx+scale*18,indexy+scale*6,scale*1,scale*5);
		g2.fillRect(indexx+scale*18,indexy+scale*8,scale*5,scale*1);
		g2.fillRect(indexx+scale*22,indexy+scale*6,scale*1,scale*3);
		g2.fillRect(indexx+scale*21,indexy+scale*9,scale*1,scale*1);
		g2.fillRect(indexx+scale*22,indexy+scale*10,scale*1,scale*1);
	}

	public Spot[][] getSpots() { return spots; }
}

