/*
 * Copyright (C) 1998  Ralf Wiebicke
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package de.rw7;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.TextArea;

class EightQueensState
{
	public int size = -1;
	public int curr;
	public int lastcollision;
	public int solnum;
	public int solmax;
	public int step;
	public int board[];

	public void setSize(int size)
	{
		this.size = size;
		curr = -1;
		lastcollision = -1;
		solnum = 0;
		solmax = 0;
		step = 0;
		board = new int[size];
	}

	public void copy(EightQueensState x)
	{
		if (x.size != size)
		{
			size = x.size;
			board = new int[size];
		}
		curr = x.curr;
		lastcollision = x.lastcollision;
		solnum = x.solnum;
		solmax = x.solmax;
		step = x.step;
		for (int i = 0; i <= curr; i++)
			board[i] = x.board[i];
	}

	/**
	   Performes a single step of backtracking. Does not care about repainting.
	   Is not synchronized due to performance reasons. When calling this method,
	   you have to care about synchronization for yourself.
	*/
	public void step()
	{
		step++;
		if ((lastcollision < 0) && (curr < (size - 1)))
		{
			curr++;
			board[curr] = 0;
		}
		else
		{
			do
			{
				board[curr]++;
				if (board[curr] >= size)
					curr--;
				else
					break;
			}
			while (curr >= 0);
			if (curr < 0)
			{
				lastcollision = -1;
				solnum = 0;
				step = 0;
				return;
			};
		};

		lastcollision = -1;
		for (int i = 0; i < curr; i++)
			if ((board[i] == board[curr])
				|| ((curr - i) == Math.abs(board[curr] - board[i])))
			{
				lastcollision = i;
				break;
			};

		if ((lastcollision < 0) && (curr == (size - 1)))
		{
			solnum++;
			if (solnum > solmax)
				solmax = solnum;
		};
	}

	public void computeSolution(int offset)
	{
		lastcollision = -1;
		for (int i = 0;(i < size) && (lastcollision < 0); i++)
		{
			board[i] = (2 * i + offset) % size;
			curr = i;
			for (int j = 0; j < curr; j++)
				if ((board[j] == board[curr])
					|| ((curr - j) == Math.abs(board[curr] - board[j])))
				{
					lastcollision = j;
					break;
				};
		}
	}

} // class EightQueensState

class EightQueensBoard extends Canvas implements Runnable
{
	private Thread running;
	private TextArea console;
	private EightQueensState state = new EightQueensState();
	private EightQueensState painted = new EightQueensState();

	EightQueensBoard(int size, TextArea console)
	{
		this.console = console;
		state.setSize(size);
	}

	void stopThreads()
	{
		synchronized (state)
		{
			stopSearch();
		}
	}

	/**
	   Does not use Thread.stop() due to deprecation in JDK 1.2
	   Method does not stop the thread directly, but sets a flag,
	   which causes the thread to abort at the next suitable posibility.
	   Calling thread must own lock of state
	   @see state
	*/
	private void stopSearch()
	{
		if (running != null)
		{
			running = null;
			try
			{
				state.wait();
			}
			catch (InterruptedException e)
			{
				System.out.println(e);
			};
		}
	}

	void setSize(int size)
	{
		if (size < 3)
			return;

		synchronized (state)
		{
			stopSearch();
			state.setSize(size);
			tell("board size is " + size + ".");
		}
		repaint();
	}

	int getBoardSize()
	{
		return state.size;
	}

	void computeSolution(int offset)
	{
		synchronized (state)
		{
			stopSearch();
			state.computeSolution(offset);
			if (state.lastcollision < 0)
				tell("pattern is a SOLUTION!");
			else
				tell("pattern is no solution.");
		}
		repaint();
	}

	private boolean searchall;
	private EightQueensState runningstate;
	void startSearchForSolution(boolean searchall)
	{
		synchronized (state)
		{
			stopSearch();
			if (searchall)
				tell("searching for all solutions...");
			else
				tell("searching for next solution...");

			this.searchall = searchall;
			if (runningstate == null)
				runningstate = new EightQueensState();
			runningstate.copy(state);

			running = new Thread(this);
			running.setPriority(Thread.MIN_PRIORITY);
			running.start();
		}
	}

	public void run()
	{
		EightQueensState x = runningstate;

		if (searchall)
			do x.step();
			while ((running != null) && (x.curr >= 0));
		else
			do x.step();
			while ((running != null)
				&& ((x.lastcollision >= 0) || (x.curr < (x.size - 1)))
				&& (x.curr >= 0));

		synchronized (state)
		{
			if (running == null)
				tell("searching stopped");
			running = null;
			state.notifyAll();

			state.copy(x);

			if (x.curr < 0)
				tell("end reached, total " + x.solmax + " solutions.");
			else if ((x.lastcollision < 0) && (x.curr == (x.size - 1)))
				tell("found solution #" + x.solnum + ".");
			else
				tell("up to now " + x.solmax + " solutions found.");

		}

		repaint();
	}

	void stepOnce()
	{
		synchronized (state)
		{
			stopSearch();
			state.step();

			if (((state.lastcollision >= 0) || (state.curr < (state.size - 1))))
				tell("stepped once.");
			else
				tell("stepped once, found solution #" + state.solnum + ".");
		};

		repaint();
	}

	/**
	   Puts some text on the console. Not synchronized due to performance.
	   Has to be synchronized on state by the calling method.
	   @see state
	*/
	private void tell(String s)
	{
		console.append("at " + state.step + ": " + s + "\n");
		console.setCaretPosition(console.getText().length());
	}

	public Dimension minimumSize()
	{
		final Dimension D = getParent().getSize();
		int i = Math.min(D.width, D.height);
		D.width = i;
		D.height = i;
		return (D);
	}

	public Dimension preferredSize()
	{
		return (minimumSize());
	}

	public void paint(Graphics g)
	{
		synchronized (state)
		{
			painted.curr = -1;
			painted.size = -1;
			this.update(g);
		};
	}

	public void update(Graphics g)
	{
		synchronized (state)
		{
			int size = state.size;
			int curr = state.curr;
			int[] board = state.board;
			final int Quad = Math.min(getSize().width, getSize().height) / size;

			if (painted.size != state.size)
			{
				g.setColor(getBackground());
				g.fillRect(0, 0, getSize().width, getSize().height);
				g.setColor(Color.white);
				g.fillRect(0, 0, size * Quad, size * Quad);
				g.setColor(Color.black);
				for (int j = 0; j < size; j++)
					for (int i = 0; i < size; i++)
						if (((i + j) & 1) > 0)
							g.fillRect(Quad * j, Quad * i, Quad, Quad);
				painted.size = size;
				painted.board = new int[size];
				painted.curr = -1;
			}

			for (int i = 0; i <= painted.curr; i++)
				if ((i > curr) || (painted.board[i] != board[i]))
				{
					g.setColor(
						(((i + painted.board[i]) & 1) == 0)
							? Color.white
							: Color.black);
					g.fillRect(Quad * painted.board[i], Quad * i, Quad, Quad);
				}

			for (int i = 0; i <= curr; i++)
			{
				g.setColor(
					((state.lastcollision >= 0)
						&& ((i == curr) || (i == state.lastcollision)))
						? Color.red
						: Color.green);
				g.fillOval(Quad * board[i], Quad * i, Quad - 1, Quad - 1);
				painted.board[i] = board[i];
			}
			painted.curr = curr;

		} // synchronized(state)
	}

} // class EightQueensBoard

public class EightQueens extends java.applet.Applet
{
	EightQueensBoard board;
	String Step = "Step Once",
		Sol = "Search for Solution",
		All = "Search for all Solutions",
		Comp = "Try Pattern",
		PgUp = "Larger Board",
		PgDn = "Smaller Board";

	TextArea console;

	public void init()
	{
		setLayout(new BorderLayout());
		{
			Panel Q = new Panel(new BorderLayout());
			console = new TextArea("Welcome to EightQueens!\n");
			console.setEditable(false);
			Q.add(console, BorderLayout.CENTER);
			{
				Panel P = new Panel(new GridLayout(0, 1));
				P.add(new Button(Step));
				P.add(new Button(Sol));
				P.add(new Button(All));
				P.add(new Button(Comp));
				P.add(new Button(PgUp));
				P.add(new Button(PgDn));
				Q.add(P, BorderLayout.SOUTH);
			}
			add(Q, BorderLayout.CENTER);
		}
		add(board = new EightQueensBoard(8, console), BorderLayout.WEST);
	}

	public void stop()
	{
		board.stopThreads();
	}

	public boolean action(Event evt, Object arg)
	{
		if (evt.target instanceof Button)
		{
			if (evt.arg == Step)
				board.stepOnce();
			else if (arg == PgUp)
				board.setSize(board.getBoardSize() + 1);
			else if (arg == PgDn)
				board.setSize(board.getBoardSize() - 1);
			else if (arg == Comp)
				board.computeSolution(0);
			else if (arg == Sol)
				board.startSearchForSolution(false);
			else if (arg == All)
				board.startSearchForSolution(true);
			else
				return (super.handleEvent(evt));
			return (true);
		}
		return (false);
	}

} // class EightQueens
