import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;

public class PuzzleField extends Canvas
{
	private Puzzle puzzle;
	private Image img;
	private Image offscreen;
	private int pos[][][];
	private int gridX;
	private int gridY;
	private int gapX;
	private int gapY;
	private int borderX;
	private int borderY;
	private int cellWidth;
	private int cellHeight;
	private int fieldWidth;
	private int fieldHeight;
	private Random random = new Random();

    public PuzzleField(Puzzle puzzle, Image img, int gridX, int gridY, int gapX, int gapY, int borderX, int borderY)
	{
		this.puzzle = puzzle;
		this.img = img;
		this.gridX = gridX;
		this.gridY = gridY;
		this.gapX = gapX;
		this.gapY = gapY;
		this.borderX = borderX;
		this.borderY = borderY;
		
		pos = new int[gridX][gridY][3];
		int width = img.getWidth(puzzle);
		int height = img.getHeight(puzzle);
		cellWidth = width / gridX;
		cellHeight = height / gridY;
		fieldWidth = width + 2 * borderX + (gridX - 1) * gapX;
		fieldHeight = height + 2 * borderY + (gridY - 1) * gapY;
		setSize(fieldWidth, fieldHeight);

		// offscreenimage anlegen
		offscreen = puzzle.createImage(fieldWidth, fieldHeight);

		restore();
    }     

    /**
     * Liefert eine Zufallszahl zwischen 0 und max - 1
     */
    private int getRandomInt(int max)
    {
        long r = random.nextInt();
        long m = max;
        long mi = Integer.MAX_VALUE + 1;
        return Math.abs((int) (r * m / mi));
    }

	public void restore()
	{
		for (int x = 0; x < gridX; x++) {
			for (int y = 0; y < gridY; y++) {
				pos[x][y][0] = x;
				pos[x][y][1] = y;
				pos[x][y][2] = 0;
			}
		}
		pos[0][0][2] = 1;
		repaint();
	}

	public void shuffle(int count)
	{
		// leeres Feld suchen
		int x = 0;
		int y = 0;
		boolean end = false;
		for (x = 0; x < gridX; x++) {
			for (y = 0; y < gridY; y++) {
				if (pos[x][y][2] == 1) {
					end = true;
					break;
				}
			}
			if (end) break;
		}

		// davon ausgehend zufällig mischen
        int old = -1;
		for (int i = 0; i < count; i++)
		{
		    while (true)
		    {
			    // Anzahl möglicher benachbarter Felder herausfinden
			    int n = 0;
			    if (x > 0) n++;
			    if (x < gridX - 1) n++;
			    if (y > 0) n++;
			    if (y < gridY - 1) n++;

			    // eins auswählen, darf nicht entgegengesetzt dem
			    // vorherigen (old) sein
			    n = getRandomInt(n);
			    if (x > 0 && n-- == 0) {
			        if (old == 1) continue;
			        old = 0;
				    swap(x, y, x - 1, y);
				    x--;
			    }
			    if (x < gridX - 1 && n-- == 0) {
			        if (old == 0) continue;
			        old = 1;
				    swap(x, y, x + 1, y);
				    x++;
			    }
			    if (y > 0 && n-- == 0) {
			        if (old == 3) continue;
			        old = 2;
				    swap(x, y, x, y - 1);
				    y--;
			    }
			    if (y < gridY - 1 && n-- == 0) {
			        if (old == 2) continue;
			        old = 3;
				    swap(x, y, x, y + 1);
				    y++;
			    }
			    break;
			}
		}
		repaint();
	}

	private void swap(int x0, int y0, int x1, int y1)
	{
		int x = pos[x0][y0][0];
		int y = pos[x0][y0][1];
		int s = pos[x0][y0][2];
		pos[x0][y0][0] = pos[x1][y1][0];
		pos[x0][y0][1] = pos[x1][y1][1];
		pos[x0][y0][2] = pos[x1][y1][2];
		pos[x1][y1][0] = x;
		pos[x1][y1][1] = y;
		pos[x1][y1][2] = s;
	}

    public boolean mouseDown(Event evt,int x,int y)
	{
	    if (puzzle.isStarted())
	    {
            // errechnen der logischen Feldarray-Koordinate
            int xp = (x - borderX - gapX / 2) / (cellWidth + gapX);
            int yp = (y - borderY - gapY / 2) / (cellHeight + gapY);

            // nur auswerten, wenn innerhalb des Spielfelds
            if (xp >= 0 && xp < gridX && yp >= 0 && yp < gridY)
		    {
			    // Bewegung nur dann, wenn es nicht das leere Feld ist
			    if (pos[xp][yp][0] >= 0)
			    {
				    // leeres angrenzendes Feld suchen
				    if (xp > 0 && pos[xp - 1][yp][2] == 1) {
					    swap(xp, yp, xp - 1, yp);
				    } else if (xp < gridX - 1 && pos[xp + 1][yp][2] == 1) {
					    swap(xp, yp, xp + 1, yp);
				    } else if (yp > 0 && pos[xp][yp - 1][2] == 1) {
					    swap(xp, yp, xp, yp - 1);
				    } else if (yp < gridY - 1 && pos[xp][yp + 1][2] == 1) {
					    swap(xp, yp, xp, yp + 1);
				    }

				    // neuen Zug anzeigen
                    repaint();
                }
            }
            
            // testen, ob gewonnen
            if (won()) puzzle.newGame();
        }

        // Status true zurückliefern, als Anzeige, daß der Mausklick ausgewertet wurde
        return true;
    }
    
    private boolean won()
    {
        boolean w = true;
		for (int x = 0; x < gridX; x++) {
			for (int y = 0; y < gridY; y++) {
				if (pos[x][y][0] != x ||
				    pos[x][y][1] != y)
				{
				        w = false;
				        break;
				}
			}
			if (!w) break;
		}
		return w && (pos[0][0][2] == 1);
	}

    // Anzeige des Puzzles
    public void paint(Graphics g)
	{
		Graphics g2 = offscreen.getGraphics();

        g2.setColor(Color.black);

		// Puzzleteile zeichnen
		for (int x = 0; x < gridX; x++) {
			for (int y = 0; y < gridY; y++) {
				int xd = x * (cellWidth + gapX) + borderX;
				int yd = y * (cellHeight + gapY) + borderY;
				if (pos[x][y][2] == 0) {
					int xs = pos[x][y][0] * cellWidth;
					int ys = pos[x][y][1] * cellHeight;
					g2.drawImage(img,
						xd,
						yd,
						xd + cellWidth,
						yd + cellHeight,
						xs,
						ys,
						xs + cellWidth,
						ys + cellHeight,
						puzzle);
				} else {
					g2.fillRect(xd,
						yd,
						cellWidth,
						cellHeight);
				}
			}
		}

		// Rand malen
        g2.setColor(Color.black);
        g2.fillRect(0, 0, borderX, fieldHeight);
        g2.fillRect(0, 0, fieldWidth, borderY);
        g2.fillRect(fieldWidth - borderX, 0, borderX, fieldHeight);
        g2.fillRect(0, fieldHeight - borderY, fieldWidth, borderY);

        // Linien zeichnen
        for (int c = 1; c <= gridX - 1; c++)
		{
            // senkrechte Linien ziehen
            g2.fillRect(c * (cellWidth + gapX) + borderX - gapX, 0, gapX, fieldHeight);
        }

        for (int c = 1; c <= gridY - 1; c++)
		{
            // waagerechte Linien ziehen
            g2.fillRect(0, c * (cellHeight + gapY) + borderY - gapY, fieldWidth, gapY);
        }

		g.drawImage(offscreen, 0, 0, puzzle);
	}

	public void update(Graphics g)
	{
		paint(g);
	}
}

