/**
 * @version $Id:$
 * @author  Jun Rekimoto
 */

import java.awt.*;
import java.applet.*;
import java.awt.event.*;


public class Pentomino extends Applet implements Runnable {
    Thread thread;
    Color color[];
    int npiece = 12;
	int offx = 30, offy = 30;
	int ps = 30, po = 1;    
	int board_x = 10, board_y = 6;
    int board[][];
    Piece piece[][];
    int bcopy[][];
    boolean show_progress = false;

    static int shape[][][] = {
		{{0, 0}, {-1, 1}, {0, 1}, {1, 1}, {0, 2}}, // +
		{{0, 0}, {1, 0}, {1, 1}, {2, 1}, {1, 2}}, // F
		{{0, 0}, {1, 0}, {1, 1}, {2, 0}, {2, 1}}, // P
		{{0, 0}, {0, 1}, {1, 1}, {0, 2}, {0, 3}}, // ト
		{{0, 0}, {1, 0}, {0, 1}, {0, 2}, {0, 3}}, // L
		{{0, 0}, {1, 0}, {2, 0}, {0, 1}, {2, 1}}, // U
		{{0, 0}, {1, 0}, {1, 1}, {2, 1}, {3, 1}}, // kagi
		{{0, 0}, {0, 1}, {1, 1}, {1, 2}, {2, 2}}, // w
		{{0, 0}, {1, 0}, {2, 0}, {1, 1}, {1, 2}}, // T
		{{0, 0}, {1, 0}, {2, 0}, {0, 1}, {0, 2}}, // V
		{{0, 0}, {1, 0}, {1, 1}, {1, 2}, {2, 2}}, // s
		{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}}, // I
	};
	
	public Pentomino() {
		this(10, 6);
	}

	public Pentomino(int col, int row) {
		setBackground(Color.black);
		color = new Color[npiece];
		for (int i = 0; i < color.length; i++)
			color[i] = Color.getHSBColor(
					((float)i)/((float)npiece),
					0.7f, 1.0f);
		board_x = col;
		board_y = row;
		npiece = shape.length;
		initBoard();
		initPiece();
	}
	
	public void init() {
		try {		
			String row = getParameter("row");
			String col = getParameter("col");
			String progress = getParameter("progress");
			if (row != null) board_x = Integer.parseInt(col);
			if (col != null) board_y = Integer.parseInt(row);
			if (progress != null && progress.equals("true"))
				show_progress = true;
		} catch (Exception e) {}
		
		initBoard();
		initPiece();
		start();
	}
	
	public Dimension getMinimumSize() {
		return new Dimension(
			offx * 2 + po * (board_x - 1) + ps * board_x,
			offy * 2 + po * (board_y - 1) + ps * board_y);
	}

	public Dimension getPreferredSize() {
		return getMinimumSize();
	}

    public void start() {
        if (thread == null) {
            thread = new Thread(this);
            thread.start();
        }
    }

	public void run() {
		solve();
		repaint();
		dbg("finish:" + total);
	}
	
	public void initBoard() {
		board = new int[board_x][board_y];
		bcopy = new int[board_x][board_y];
		for (int i = 0; i < board_x; i++)
			for (int j = 0; j < board_y; j++)
				bcopy[i][j] = board[i][j] = -1;
	}

	/** piece を左上角に寄せる **/
	protected void leftupper(int p[][]) {
		int minx = 100, miny = 100;
		for (int i = 0; i < p.length; i++) {
			if (p[i][0] < minx) minx = p[i][0];
			if (p[i][1] < miny) miny = p[i][1];
		}
		for (int i = 0; i < p.length; i++) {
			p[i][0] -= minx;
			p[i][1] -= miny;
		}
	}

	/** piece形状を左右反転 **/
	protected int[][] flip(int p[][]) {
		int np[][] = new int[p.length][2];
		for (int i = 0; i < p.length; i++) {
			np[i][0] = -p[i][0];
			np[i][1] = p[i][1];
		}
		leftupper(np);
		return np;
	}

	/** pieceを90度回転 */
	protected int[][] rotate(int p[][]) {
		int np[][] = new int[p.length][2];
		for (int i = 0; i < p.length; i++) {
			np[i][0] = -p[i][1];  np[i][1] = p[i][0];
		}
		leftupper(np);
		return np;
	}

	/** pieceの可能な置き方をリストアップする **/
	public void initPiece() {
		piece = new Piece[shape.length][];
		for (int i = 0; i < shape.length; i++) {
			piece[i] = initPieceOne(i, shape[i]);
			//dbg("piece " + i + " has " + piece[i].length + "shapes");//XXX
		}
	}

	public Piece[] initPieceOne(int id, int shape[][]) {
		int n = 0;
		Piece[] piece = new Piece[4 * 2];
		boolean duplicate = false;

		int tshape[][] = shape;

		// 回転させながら重複しない置き方をリストアップする
		for (int j = 0; j < 4; j++) {
			tshape = rotate(tshape);
			Piece npiece = new Piece(id, tshape);
			duplicate = false;
			for (int k = 0; k < n; k++) {
				if (npiece.equals(piece[k])) {
					duplicate = true;
					break;
				}
			}
			if (!duplicate)
				piece[n++] = npiece;
		}

		// 反転形状についても同様な処理
		tshape = flip(shape);
		for (int j = 0; j < 4; j++) {
			tshape = rotate(tshape);
			Piece npiece = new Piece(id, tshape);
			duplicate = false;
			for (int k = 0; k < n; k++) {
				if (npiece.equals(piece[k])) {
					duplicate = true;
					break;
				}
			}
			if (!duplicate)
				piece[n++] = npiece;
		}

		Piece npiece[] = new Piece[n];
		for (int i = 0; i < n; i++) {
			piece[i].resetOrigin();
			npiece[i] = piece[i];
		}
		return npiece;
	}
	
	static void dbg(String s) {System.out.println("Pent:"+s);}
	
	boolean placed[];
	int total = 0;
	long init_time;

	/** 解を求める **/	
	public void solve() {
		placed = new boolean[npiece];
		total = 0;
		init_time = System.currentTimeMillis();
		solveOneLevel(0, 0, 0);
	}
	
	void copyBoard() {
		synchronized (bcopy) {
		for (int x = 0; x < board_x; x++)
			for (int y = 0; y < board_y; y++)
				bcopy[x][y] = board[x][y];
		}
	}

	public void solveOneLevel(int level, int x, int y) {
		for (int i = 0; i < npiece; i++) {
			if (placed[i]) continue;

			// 対象や回転による重複解を避ける (「+」 ピースの位置を限定)
			if (i == 0 && (y >= (board_y +1)/ 2 || x >= ((board_x +1)/ 2) - 1))
				continue;

			for (int j = 0; j < piece[i].length; j++) {
				if (piece[i][j].place(board, x, y)) {
					if (show_progress && level >= npiece-2) {
						repaint();
					}
					if (level >= npiece-1) { // solved 
						total++;
						long t = System.currentTimeMillis();
						dbg("fond:" + total + " " + (t - init_time));//
						copyBoard();
						repaint();
						try {Thread.sleep(50);} catch (InterruptedException e) {}						
					} else {					
						int x2 = 0, y2 = 0;
						boolean found = false;
						for (int xx = x; (!found) && xx < board_x; xx++) {
							for (int yy = 0; (!found) && yy < board_y; yy++) {
								if (board[xx][yy] == -1) {
									x2 = xx;  y2 = yy;
									found = true;
								}
							}
						}
						if (found) {
							placed[i] = true;
							solveOneLevel(level+1, x2, y2);
							placed[i] = false;
						}
					}
					piece[i][j].remove(board);
				}
			}
		}
	}

	public synchronized void paint(Graphics g) {
		
		for (int i = 0; i < placed.length; i++) {
			if (placed[i]) {
				g.setColor(color[i]);
				g.fillRect(offx + i*4, 8, 4, 4);
			}
		}
				
		synchronized (bcopy) {
		for (int i = 0; i < board_x; i++) {
			for (int j = 0; j < board_y; j++) {
				int x = offx + i * (ps + po);
				int y = offy + j * (ps + po);
				if (bcopy[i][j] < 0) {
					g.setColor(Color.darkGray);
					g.fillRect(x, y, ps, ps);
				} else {				
					g.setColor(color[bcopy[i][j]]);
					g.fillRect(x, y, ps, ps);
					if (i < board_x - 1 && bcopy[i][j] == bcopy[i+1][j])
						g.fillRect(x+ps, y, po, ps);
					if (j < board_y - 1 && bcopy[i][j] == bcopy[i][j+1])
						g.fillRect(x, y + ps, ps, po);
					if (i < board_x - 1 && j < board_y - 1 &&
						bcopy[i][j] == bcopy[i+1][j+1] &&
						bcopy[i][j] == bcopy[i+1][j] && 
						bcopy[i][j] == bcopy[i][j+1])
						g.fillRect(x + ps, y + ps, po, po);
				}
			}
		}
		}
		g.setColor(Color.white);
		g.drawString("No." + total, 30, offy - 5);
	}
	
	protected Graphics offg;
	protected Image offscreen;
	protected int imagewidth, imageheight;

	/** オフスクリーンイメージを作る **/
	protected void checkOffscreen(Dimension d) {
		if ((offscreen == null) ||
			((imagewidth != d.width) || (imageheight != d.height))) {
			offscreen = this.createImage(d.width, d.height);
			imagewidth = d.width;
			imageheight = d.height;
			offg = offscreen.getGraphics();
		}
	}

	public void update(Graphics g) {
		Dimension d = getSize();
		checkOffscreen(d);
		offg.clearRect(0, 0, d.width, d.height);
		paint(offg);
		g.drawImage(offscreen, 0, 0, this);
	}	

	public static void main(String argv[]) {
		Frame f = new Frame();
		Pentomino app = new Pentomino(20, 3);
		f.add(app);
		app.init();
		f.pack();
		f.show();
	}
}


