package tetris;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class TetrisBoard extends JComponent {
    private int width, height;
    private TetrisBlock blocks[][];

    private TetrisPiece curPiece;
    private Point curOffset;

    public static final int CELL_WIDTH  = 20;
    public static final int CELL_HEIGHT = 20;

    public TetrisBoard(int width, int height) {
	if (width < 4 || height < 4)
	    throw new RuntimeException("Board too small: " + width + "x" + height);

	this.width = width;
	this.height = height;
	this.blocks = new TetrisBlock[width][height];

	this.curPiece = null;
	this.curOffset = null;

	for (int i=0; i<width; ++i)
	    for (int j=0; j<height; ++j)
		this.blocks[i][j] = null;
    }

    public Dimension getPreferredSize() {
	return new Dimension(width * CELL_WIDTH, height * CELL_HEIGHT);
    }

    private boolean tryPiece(TetrisPiece p, Point offset) {
	int offX = (int) offset.getX();
	int offY = (int) offset.getY();
	Iterator pieceOffsets = p.getOffsets();

	while (pieceOffsets.hasNext()) {
	    Point newOff = (Point) pieceOffsets.next();
	    int newX = offX + (int) newOff.getX();
	    int newY = offY + (int) newOff.getY();

	    if (newX < 0 || newY < 0)
		return false;
	    if (newX >= width || newY >= height)
		return false;
	    if (blocks[newX][newY] != null)
		return false;
	}

	this.curPiece = p;
	this.curOffset = offset;
	return true;
    }

    private boolean tryMove(int x, int y) {
	int curX = (int) curOffset.getX();
	int curY = (int) curOffset.getY();

	int newX = curX + x;
	int newY = curY + y;

	Point newOff = new Point(newX, newY);

	return tryPiece(curPiece, newOff);
    }

    private void ensureNewPiece() {
	if (curPiece != null)
	    return;

	int hPos = width/2 - 2;
	if (!tryPiece(PieceFactory.getPiece(), new Point(hPos, 0))) {
	    System.out.println("Game over.");
	    System.exit(0);
	}
    }

    private void eatLines() {
	for (int line=height-1; line>=0; --line) {
	    boolean lineComplete = true;
	    for (int i=0; i<width; ++i)
		if (blocks[i][line] == null) lineComplete = false;
	    if (lineComplete) {
		for (int i=0; i<width; ++i) {
		    for (int j=line; j>0; --j)
			blocks[i][j]=blocks[i][j-1];
		    blocks[i][0] = null;
		}
		++line;
	    }
	}
    }

    public void tick() {
	if (curPiece != null && !tryMove(0, 1)) {
	    int offX = (int) curOffset.getX();
	    int offY = (int) curOffset.getY();
	    Iterator pieceOffsets = curPiece.getOffsets();
	    TetrisBlock b = curPiece.getBlock();

	    while (pieceOffsets.hasNext()) {
		Point pt = (Point) pieceOffsets.next();
		int posX = offX + (int) pt.getX();
		int posY = offY + (int) pt.getY();
		blocks[posX][posY] = b;
	    }

	    curPiece = null;
	    curOffset = null;
	    eatLines();
	}

	ensureNewPiece();
	repaint();
    }

    public void keyPress(int key) {
	if (curPiece == null)
	    return;
	if (key == KeyEvent.VK_RIGHT)
	    tryMove(1, 0);
	if (key == KeyEvent.VK_LEFT)
	    tryMove(-1, 0);
	if (key == KeyEvent.VK_DOWN)
	    tryPiece(curPiece.rotate(), curOffset);
	if (key == KeyEvent.VK_SPACE)
	    while (tryMove(0, 1));
	repaint();
    }

    public void paint(Graphics g) {
	g.fillRect(0, 0, width*CELL_WIDTH, height*CELL_HEIGHT);

	for (int i = 0; i < width; ++i) {
	    for (int j = 0; j < height; ++j) {
		if (blocks[i][j] != null) {
		    g.translate(i*CELL_WIDTH, j*CELL_HEIGHT);
		    blocks[i][j].paint(g, CELL_WIDTH, CELL_HEIGHT);
		    g.translate(-i*CELL_WIDTH, -j*CELL_HEIGHT);
		}
	    }
	}

	if (curPiece != null) {
	    int offX = (int) curOffset.getX();
	    int offY = (int) curOffset.getY();
	    Iterator pieceOffsets = curPiece.getOffsets();
	    TetrisBlock b = curPiece.getBlock();

	    while (pieceOffsets.hasNext()) {
		Point pos = (Point) pieceOffsets.next();

		int posX = offX + (int) pos.getX();
		int posY = offY + (int) pos.getY();

		g.translate(posX*CELL_WIDTH, posY*CELL_HEIGHT);
		b.paint(g, CELL_WIDTH, CELL_HEIGHT);
		g.translate(-posX*CELL_WIDTH, -posY*CELL_HEIGHT);
	    }
	}
    }
}
