import java.awt.*;

abstract class Maze extends Canvas
{
    static final byte TOP = 0x01;
    static final byte RIGHT = 0x02;
    static final byte BOTTOM = 0x04;
    static final byte LEFT = 0x08;
    static final byte PATHON = 0x10;
    static final byte DIRTY = (byte) 0x80;

    protected byte [][] maze = null, path = null;
    protected int mazeWidth, mazeHeight, squareWidth, squareHeight,
	leftOffset, topOffset, pxoff, pyoff;
    protected int minXdirty, minYdirty, maxXdirty, maxYdirty;
    protected int startX, startY, finishX, finishY, destX, destY;
    protected int lineWidth = 1, lineHeight = 1, sqLineWidth, sqLineHeight,
	pathWidth, pathHeight;
    public boolean generating = false, solving = true;

    protected boolean displayPath = true;

    protected abstract byte initSq ();

    abstract boolean isOpen (int x, int y);
    abstract void generate (boolean displaySearch);
    abstract void stopGenerating ();
    abstract boolean traverse (int startX, int startY, int finishX,
			       int finishY, boolean displaySearch);
    abstract boolean traverse (int destX, int destY);
    abstract boolean won ();
    protected abstract void drawSquare (int x, int y);
    protected abstract void drawPathSquare (int px, int py, Color color);

    public boolean traverse (boolean displaySearch)
    {
	return traverse(startX, startY, finishX, finishY, displaySearch);
    }

    public byte sqr (int x, int y)
    {
	return (byte) (maze[x][y] & ~DIRTY);
    }

    public boolean inBounds (int x, int y)
    {
	return (x >= 0 && x < mazeWidth && y >= 0 && y < mazeHeight);
    }

    public Dimension getDimensions ()
    {
	return new Dimension(mazeWidth, mazeHeight);
    }

    public synchronized void setDimensions (int squaresWide, int squaresHigh)
    {
	if (mazeWidth != squaresWide || mazeHeight != squaresHigh) {
	    mazeWidth = Math.max(3, squaresWide);  // min maze width is 3 sqrs
	    mazeHeight = Math.max(3, squaresHigh);  // min maze height is 3 sqrs
	    maze = null;
	    resetMaze();
	}
    }

    protected long timer, maxFrameRate = 30L;
    protected void showMaze (boolean allDirty)
    {
	if (allDirty || offscreenImage == null)
	    repaint();
	else
	    repaint(leftOffset + minXdirty * squareWidth,
		    topOffset + minYdirty * squareHeight,
		    squareWidth * (maxXdirty - minXdirty + 1) + lineWidth,
		    squareHeight * (maxYdirty - minYdirty + 1) + lineHeight);
	try { wait(); } catch (InterruptedException e) {}
	long t = System.currentTimeMillis();
	if ((timer -= t - (1000L / maxFrameRate)) > 0)
	    try { Thread.sleep(timer); } catch (InterruptedException e) {}
	timer = System.currentTimeMillis();
    }

    protected void resetMaze ()
    {
	Dimension d = getSize();
	
	if (d.width <= 0  ||  d.height <= 0  ||  mazeWidth <= 0  ||  mazeHeight <= 0) {
	    return;
	}
	squareWidth = Math.max(2, (d.width - lineWidth) / mazeWidth);
	squareHeight = Math.max(2, (d.height - lineHeight) / mazeHeight);
	if (lineWidth >= squareWidth)  lineWidth = squareWidth - 1;
	if (lineHeight >= squareHeight)  lineHeight = squareHeight - 1;
	sqLineWidth = squareWidth + lineWidth;
	sqLineHeight = squareHeight + lineHeight;
	leftOffset = (d.width - (squareWidth * mazeWidth) - lineWidth) >> 1;
	topOffset = (d.height - (squareHeight * mazeHeight) - lineHeight) >> 1;
	
	int pw = squareWidth - lineWidth;
	pathWidth = (pw & 1) == 0 ? Math.max(2, (pw >> 1) & ~1) : Math.max(1, (pw >> 1) | 1);
	int ph = squareHeight - lineHeight;
	pathHeight = (ph & 1) == 0 ? Math.max(2, (ph >> 1) & ~1) : Math.max(1, (ph >> 1) | 1);
	pxoff = (sqLineWidth - pathWidth) >> 1;
	pyoff = (sqLineHeight - pathHeight) >> 1;

	offscreenImage = null;  // reallocate offscreenImage

	if (maze == null)
	    clearMaze();
    }

    protected int dirtySquare (int x, int y)
    {
	if (x < minXdirty) minXdirty = x;
	if (x > maxXdirty) maxXdirty = x;
	if (y < minYdirty) minYdirty = y;
	if (y > maxYdirty) maxYdirty = y;
	return maze[x][y] |= DIRTY;
    }

    protected void clearMaze ()
    {
	if (maze == null)
	    maze = new byte[mazeWidth][mazeHeight];
	byte sq = initSq();
	for (int xx = mazeWidth;  --xx >= 0; )
	    for (int yy = mazeHeight;  --yy >= 0; ) {
		maze[xx][yy] = sq;
	    }
	minXdirty = minYdirty = 0;
	maxXdirty = mazeWidth - 1;
	maxYdirty = mazeHeight - 1;
	startX = finishX = -1;
	//path = null;
    }

    public void setBounds (int x, int y, int width, int height)
    {
	Dimension d1 = getSize();
	super.setBounds (x, y, width, height);
	Dimension d2 = getSize();
	if (d1.width != d2.width  ||  d1.height != d2.height) {
	    resetMaze();
	    repaint();
	}
    }

    protected Image offscreenImage = null;
    protected Graphics offscr;

    public synchronized boolean activatePath (int x, int y) {
	path [x] [y] = PATHON;
	boolean left = inBounds (x-1, y) && path [x-1] [y] != 0
	    && (maze [x] [y] & LEFT) == 0,
	    right = inBounds (x+1, y) && path [x+1] [y] != 0
	    && (maze [x] [y] & RIGHT) == 0,
	    up = inBounds (x, y-1) && path [x] [y-1] != 0
	    && (maze [x] [y] & TOP) == 0,
	    down = inBounds (x, y+1) && path [x] [y+1] != 0
	    && (maze [x] [y] & BOTTOM) == 0;

	if (left) {
	    path [x] [y] |= LEFT;
	    path [x-1] [y] |= RIGHT;
	    dirtySquare (x-1, y);
	}
	if (right) {
	    path [x] [y] |= RIGHT;
	    path [x+1] [y] |= LEFT;
	    dirtySquare (x+1, y);
	}
	if (up) {
	    path [x] [y] |= TOP;
	    path [x] [y-1] |= BOTTOM;
	    dirtySquare (x, y-1);
	}
	if (down) {
	    path [x] [y] |= BOTTOM;
	    path [x] [y+1] |= TOP;
	    dirtySquare (x, y+1);
	}
	dirtySquare (x, y);
	return true;
    }

    public void setDest (int x, int y)
    {
	destX = (x-leftOffset) / squareWidth;
	destY = (y-topOffset) / squareHeight;
    }

    public void pathToSquare (int x, int y)
    {
	activatePath (x, y);
    }

    protected void drawTarget (int x, int y, Color color) {
	offscr.setColor(color);
	offscr.fillOval (x * squareWidth + leftOffset + pxoff,
			 y * squareHeight + topOffset + pyoff,
			 pathWidth+1, pathHeight+1);
    }

    public void paintOffscreenImage ()
    {
	boolean doAll = (offscreenImage == null);
	if (doAll) {
	    Dimension d = getSize ();
	    offscreenImage = createImage (d.width, d.height);
	    offscr = offscreenImage.getGraphics ();
	    offscr.setColor (getBackground ());
	    offscr.fillRect (0, 0, d.width, d.height);
	    minXdirty = minYdirty = 0;
	    maxXdirty = mazeWidth-1;
	    maxYdirty = mazeHeight-1;
	}
	
	offscr.setColor (Color.black);

	for (int x = minXdirty; x <= maxXdirty; x ++) {
	    for (int y = minYdirty; y <= maxYdirty; y ++) {
		if (doAll || (maze [x] [y] & DIRTY) != 0) {
		    drawSquare (x, y);
		    maze [x] [y] = sqr (x, y);

		    if (displayPath)
			drawPathSquare(x, y, Color.blue);
		    if (x == startX  &&  y == startY)
			drawTarget(x, y, Color.green);
		    if (x == finishX  &&  y == finishY)
			drawTarget(x, y, Color.red);
		}
	    }
	}

	maxXdirty = maxYdirty = -1;
	minXdirty = minYdirty = Integer.MAX_VALUE;

	if (won ()) {
	    Dimension d = getSize ();
	    offscr.setColor (Color.black);
	    int fontSize = d.width > d.height ? d.height / 5 :
		d.width / 10;
	    offscr.setFont (new Font ("Times New Roman", Font.BOLD, fontSize));
	    offscr.drawString ("You Win!", d.width/2-fontSize*2,
			       d.height /2);
	}
    }

    public synchronized void paint (Graphics g)
    {
	paintOffscreenImage ();
	if (offscreenImage != null)
	    g.drawImage (offscreenImage, 0, 0, this);
	notifyAll ();
    }

    public void update (Graphics g)
    {
	paint (g);
    }
}
