
///////////////////////////////////////////////////////////////////////////
// This program is free software: you can redistribute it and/or modify  //
// it under the terms of the version 3 of the GNU General Public License //
// as published by the Free Software Foundation.                         //
//                                                                       //
// 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, see <http://www.gnu.org/licenses/>.  //
//                                                                       //
// Written by and Copyright (C) Francois Fleuret                         //
// Contact <francois@fleuret.org> for comments & bug reports             //
///////////////////////////////////////////////////////////////////////////

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.lang.Object;

class Square {
    char solution;
    char found;
    boolean ajour;
    boolean holdCursor;
    boolean locked;
    Color color;

    Square(char c) {
	solution = c;
	found = ' ';
	ajour = false;
	locked = false;
	color = Color.black;
    }

    void setFound(char c) { if(c != found) { found = c; ajour = false; } }

    void putCursor() { if(!holdCursor) ajour = false; holdCursor = true; }

    void remCursor() { if(holdCursor) ajour = false; holdCursor = false; }

    void lock(Color c) {
	if(!locked) {
	    ajour = false;
	    locked = true;
	    color = c;
	}
    }
}

class Grid extends Panel implements KeyListener, MouseListener {
    int gridWidth, gridHeight;
    int xCursor, yCursor;
    int nbLetters, nbFound;
    boolean cursorHorizontal;

    int xMargin, yMargin;
    Square grid[][];
    Font font, smallFont;
    FontMetrics fontMetrics, smallFontMetrics;
    int squareSize;

    TextArea definitionArea;
    String[][][] definitions;

    Grid(Font f, Font sf,
	 FontMetrics fm, FontMetrics sfm,
	 int w, int h,
	 String content,
	 TextArea ta, String def) {

	int x, y, xx, yy, k;

	font = f; fontMetrics = fm;
	smallFont = sf; smallFontMetrics = sfm;
	squareSize = fontMetrics.getHeight();

	gridWidth = w;
	gridHeight = h;
	grid = new Square[gridWidth][gridHeight];

	yMargin = smallFontMetrics.getHeight();
	xMargin = yMargin;

	nbLetters = 0; nbFound = 0;
	k = 0;
	for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++) {
	    if(k<content.length())
		grid[x][y] = new Square(content.charAt(k++));
	    else
		grid[x][y] = new Square('.');
	    if(grid[x][y].solution != '.') nbLetters++;
	}

	xCursor = 0; yCursor = 0;
	cursorHorizontal = true;
	grid[xCursor][yCursor].putCursor();

	if(ta != null) {
	    definitionArea = ta;
	    definitions = new String[gridWidth][gridHeight][2];

	    String direction, sd;
	    int i, j;

	    direction="h";

	    for(y=0; y<gridHeight; y++) for(x=0; x<gridWidth; x++) {
		definitions[x][y][0] = "";
		definitions[x][y][1] = "";
	    }

	    j = 0;
	    while(j < def.length()-1) {

		i = j; j = def.indexOf('/', i);
		if((j > 0) && ((def.indexOf(':', i) > j) || (def.indexOf(':', i) < 0))) {
		    direction = def.substring(i, j).toLowerCase();
		    j++;
		} else j = i;


		i = j;
		y = 0;
		while((def.charAt(i) >= 'a') && (def.charAt(i) <= 'z'))
		    y = y*26 + ((int) def.charAt(i++)) - 'a';

		x = 0;
		while((def.charAt(i) >= '0') && (def.charAt(i) <= '9'))
		    x = x*10 + ((int) def.charAt(i++)) - '0';
		x--;
		i++;

		j = def.indexOf('/', i);
		if(j < 0) j = def.length();
		sd = def.substring(i, j);
		j++;

		if(direction.equals("horizontal")) {
		    for(xx = x; (xx>=0) && (grid[xx][y].solution != '.'); xx--)
			definitions[xx][y][0] = sd;
		    for(xx = x; (xx<gridWidth) && (grid[xx][y].solution != '.'); xx++)
			definitions[xx][y][0] = sd;
		} else if(direction.equals("vertical")) {
		    for(yy = y; (yy>=0) && (grid[x][yy].solution != '.'); yy--)
			definitions[x][yy][1] = sd;
		    for(yy = y; (yy<gridHeight) && (grid[x][yy].solution != '.'); yy++)
			definitions[x][yy][1] = sd;
		} else System.out.println("Unknown orientation '" + direction + "'");
	    }
	}

	addKeyListener(this);
	addMouseListener(this);
    }

    public void giveWord() {
	int xx, yy;

	int x = xCursor;
	int y = yCursor;

	if(cursorHorizontal) {
	    for(xx = x; (xx>=0) && (grid[xx][y].solution != '.'); xx--) {
		grid[xx][y].setFound(grid[xx][y].solution);
		grid[xx][y].lock(Color.red);
	    }

	    for(xx = x; (xx<gridWidth) && (grid[xx][y].solution != '.'); xx++) {
		grid[xx][y].setFound(grid[xx][y].solution);
		grid[xx][y].lock(Color.red);
	    }
	} else {
	    for(yy = y; (yy>=0) && (grid[x][yy].solution != '.'); yy--) {
		grid[x][yy].setFound(grid[x][yy].solution);
		grid[x][yy].lock(Color.red);
	    }

	    for(yy = y; (yy<gridHeight) && (grid[x][yy].solution != '.'); yy++) {
		grid[x][yy].setFound(grid[x][yy].solution);
		grid[x][yy].lock(Color.red);
	    }
	}

	nbFound = 0;
	for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
	    if(grid[x][y].solution == grid[x][y].found) nbFound++;

	if(nbFound == nbLetters) {
	    for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
		grid[x][y].lock(Color.blue);
	}

	repaint();
    }

    public void checkWord() {
	boolean ok;
	int xmin, xmax, xx;
	int ymin, ymax, yy;

	int x = xCursor;
	int y = yCursor;

	if(cursorHorizontal) {
	    ok = true;
	    for(xmin = x; ok && (xmin>=0) && (grid[xmin][y].solution != '.'); xmin--)
		ok &= (grid[xmin][y].solution == grid[xmin][y].found);

	    for(xmax = x; ok && (xmax<gridWidth) && (grid[xmax][y].solution != '.'); xmax++)
		ok &= (grid[xmax][y].solution == grid[xmax][y].found);

	    xmin++; xmax--;

	    if(ok) for(xx = xmin; xx<=xmax; xx++) grid[xx][y].lock(Color.green);

	} else {
	    ok = true;
	    for(ymin = y; ok && (ymin>=0) && (grid[x][ymin].solution != '.'); ymin--)
		ok &= (grid[x][ymin].solution == grid[x][ymin].found);

	    for(ymax = y; ok && (ymax<gridHeight) && (grid[x][ymax].solution != '.'); ymax++)
		ok &= (grid[x][ymax].solution == grid[x][ymax].found);

	    ymin++; ymax--;

	    if(ok) for(yy = ymin; yy<=ymax; yy++) grid[x][yy].lock(Color.green);
	}

	repaint();
    }

    public synchronized void drawSquare(Graphics g, int x, int y) {
	int arrowMargin = 2;
	int xe, ye, deltaX;
	int xf[], yf[];
	char tmp[];
	tmp = new char[1];
	xe = x*squareSize+xMargin; ye = y*squareSize+yMargin;
	g.setFont(font);

	if(grid[x][y].solution != '.') {
	    g.setColor(Color.white);
	    g.fillRect(xe, ye, squareSize, squareSize);

	    if(grid[x][y].holdCursor) {
		xf = new int[5]; yf = new int[5];
		g.setColor(Color.gray);
		if(cursorHorizontal) {
		    xf[0] = xe + 1 + arrowMargin;
		    yf[0] = ye + 1 + arrowMargin;
		    xf[1] = xe + squareSize/2;
		    yf[1] = ye + 1 + arrowMargin;
		    xf[2] = xe + squareSize - 1 - arrowMargin;
		    yf[2] = ye + squareSize/2;
		    xf[3] = xe + squareSize/2;
		    yf[3] = ye + squareSize - arrowMargin;
		    xf[4] = xe + 1 + arrowMargin;
		    yf[4] = ye + squareSize - arrowMargin;
		    g.fillPolygon(xf, yf, 5);
		}
		else
		    {
			xf[0] = xe + squareSize - arrowMargin;
			yf[0] = ye + 1 + arrowMargin;
			xf[1] = xe + squareSize - arrowMargin;
			yf[1] = ye + squareSize/2;
			xf[2] = xe + squareSize/2;
			yf[2] = ye + squareSize - 1 - arrowMargin;
			xf[3] = xe + 1 + arrowMargin;
			yf[3] = ye + squareSize/2;
			xf[4] = xe + 1 + arrowMargin;
			yf[4] = ye + 1 + arrowMargin;
			g.fillPolygon(xf, yf, 5);
		    }
	    }

	    g.setColor(Color.black);
	    g.drawRect(xe, ye, squareSize, squareSize);
	    tmp[0] = grid[x][y].found;
	    deltaX = (squareSize - fontMetrics.charWidth(tmp[0]))/2;

	    if(grid[x][y].locked) g.setColor(grid[x][y].color);
	    g.drawChars(tmp, 0, 1, xe + deltaX, ye+fontMetrics.getAscent());

	} else {
	    g.setColor(Color.black);
	    g.fillRect(xe, ye, squareSize+1, squareSize+1);
	}
	grid[x][y].ajour = true;
    }

    public synchronized void update(Graphics g) {
	int x, y;
	for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
	    if(!grid[x][y].ajour) drawSquare(g, x, y);
    }

    public synchronized void paint(Graphics g) {
	int x, y;
	char tmp[];

	g.setFont(smallFont);
	g.setColor(Color.black);

	tmp = new char[1];
	for(y = 0; y<gridHeight; y++) {
	    tmp[0] = (char) (y + (int) 'a');
	    g.drawChars(tmp, 0, 1,
			xMargin - smallFontMetrics.charWidth(tmp[0]) - 3,
			y*squareSize + yMargin + smallFontMetrics.getAscent());
	}

	for(x = 0; x<gridWidth; x++)
	    g.drawString("" + (x+1),
			 xMargin + x*squareSize + 3,
			 yMargin - smallFontMetrics.getDescent() - 1);

	for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
	    drawSquare(g, x, y);
    }

    public void keyTyped(KeyEvent keyEvent) {}
    public void keyReleased(KeyEvent keyEvent) {}

    public void keyPressed(KeyEvent keyEvent) {
	int x, y;
	int code = keyEvent.getKeyCode();
	char key = keyEvent.getKeyChar();

	if(code == KeyEvent.VK_UP) {
	    y = yCursor-1;
	    while((y > 0) && (grid[xCursor][y].solution == '.')) y--;
	    if((y >= 0) && (grid[xCursor][y].solution != '.')) {
		grid[xCursor][yCursor].remCursor();
		yCursor = y;
		grid[xCursor][yCursor].putCursor();
	    }
	}
	else if(code == KeyEvent.VK_DOWN) {
	    y = yCursor+1;
	    while((y < gridHeight-1) && (grid[xCursor][y].solution == '.')) y++;
	    if((y < gridHeight) && (grid[xCursor][y].solution != '.')) {
		grid[xCursor][yCursor].remCursor();
		yCursor = y;
		grid[xCursor][yCursor].putCursor();
	    }
	}
	else if(code == KeyEvent.VK_LEFT) {
	    x = xCursor-1;
	    while((x > 0) && (grid[x][yCursor].solution == '.')) x--;
	    if((x >= 0) && (grid[x][yCursor].solution != '.')) {
		grid[xCursor][yCursor].remCursor();
		xCursor = x;
		grid[xCursor][yCursor].putCursor();
	    }
	}
	else if(code == KeyEvent.VK_RIGHT) {
	    x = xCursor+1;
	    while((x < gridWidth-1) && (grid[x][yCursor].solution == '.')) x++;
	    if((x < gridWidth) && (grid[x][yCursor].solution != '.')) {
		grid[xCursor][yCursor].remCursor();
		xCursor = x;
		grid[xCursor][yCursor].putCursor();
	    }
	}
	else if(code == Event.ENTER) {
	    cursorHorizontal = !cursorHorizontal;
	    grid[xCursor][yCursor].ajour = false;
	}
	else if(code == Event.BACK_SPACE) {
	    if((grid[xCursor][yCursor].found == ' ') || grid[xCursor][yCursor].locked) {
		if(cursorHorizontal) {
		    if(xCursor > 0)
			if(grid[xCursor - 1][yCursor].solution != '.') {
			    grid[xCursor][yCursor].remCursor();
			    xCursor--;
			    grid[xCursor][yCursor].putCursor();
			}
		} else {
		    if(yCursor > 0)
			if(grid[xCursor][yCursor - 1].solution != '.') {
			    grid[xCursor][yCursor].remCursor();
			    yCursor--;
			    grid[xCursor][yCursor].putCursor();
			}
		}
	    }

	    if(!grid[xCursor][yCursor].locked) {
		if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
		    nbFound--;
		grid[xCursor][yCursor].setFound(' ');
	    }
	} else if(key == ' ') {
	    if(definitionArea != null) {
		if(cursorHorizontal) definitionArea.setText(definitions[xCursor][yCursor][0]);
		else                 definitionArea.setText(definitions[xCursor][yCursor][1]);
	    }
	} else if((key >= 'a') && (key <= 'z')) {
	    if(!grid[xCursor][yCursor].locked) {
		if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
		    nbFound--;
		grid[xCursor][yCursor].setFound((char) key);
		if(grid[xCursor][yCursor].found == grid[xCursor][yCursor].solution)
		    nbFound++;
	    }

	    if(cursorHorizontal) {
		if(xCursor < gridWidth-1)
		    if(grid[xCursor + 1][yCursor].solution != '.') {
			grid[xCursor][yCursor].remCursor();
			xCursor++;
			grid[xCursor][yCursor].putCursor();
		    }
	    } else {
		if(yCursor < gridHeight-1)
		    if(grid[xCursor][yCursor + 1].solution != '.') {
			grid[xCursor][yCursor].remCursor();
			yCursor++;
			grid[xCursor][yCursor].putCursor();
		    }
	    }

	    if(nbFound == nbLetters) {
		for(y = 0; y<gridHeight; y++) for(x = 0; x<gridWidth; x++)
		    grid[x][y].lock(Color.blue);
	    }
	}

	repaint();
    }

    public void mousePressed(MouseEvent mouseEvent) {
	int xc, yc;
	xc = (mouseEvent.getX()-xMargin)/squareSize;
	if(xc < 0) xc = 0;
	if(xc >= gridWidth) xc = gridWidth-1;
	yc = (mouseEvent.getY()-yMargin)/squareSize;
	if(yc < 0) yc = 0;
	if(yc >= gridHeight) yc = gridHeight-1;

	if(grid[xc][yc].solution != '.') {
	    grid[xCursor][yCursor].remCursor();
	    xCursor = xc; yCursor = yc;
	    grid[xCursor][yCursor].putCursor();
	}

	repaint();
    }

    public void mouseClicked(MouseEvent mouseEvent) {}
    public void mouseReleased(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}

    public Dimension getPreferredSize() {
	return new Dimension(xMargin + gridWidth*squareSize + 1,
			     yMargin + gridHeight*squareSize + 1);
    }

}

public class Words extends Applet implements ActionListener {
    Grid grid;
    Button checkButton, giveButton;
    TextArea definitionArea;

    public void init() {
	String fontName, s, definitionString;
	int fontSize;

	Font f, sf;
	FontMetrics fm, sfm;
	int w, h, wd, hd;

	s = getParameter("bgcolor");
	if(s != null)
	    setBackground(new Color(Integer.parseInt(s)));

	fontName = getParameter("font");
	if(fontName == null) fontName = "Courier";

	s = getParameter("fontsize");
	if(s == null) fontSize = 48; else fontSize = Integer.parseInt(s);

	f = new Font(fontName, Font.PLAIN, fontSize);

	fm = getGraphics().getFontMetrics(f);

	fontName = getParameter("smallfont");
	if(fontName == null) fontName = "Courier";

	s = getParameter("smallfontsize");
	if(s == null) fontSize = 20; else fontSize = Integer.parseInt(s);

	sf = new Font(fontName, Font.ITALIC, fontSize);
	sfm = getGraphics().getFontMetrics(sf);

	s = getParameter("gridwidth");
	if(s == null) w = 1; else w = Integer.parseInt(s);

	s = getParameter("gridheight");
	if(s == null) h = 1; else h = Integer.parseInt(s);

	definitionString = null;

	s = getParameter("definitions");

	if(s != null) {
	    int i, j;
	    wd = -1; hd = -1;

	    // Find the width and the height
	    i = 0; j = s.indexOf('x', i);
	    wd = Integer.parseInt(s.substring(i, j));
	    i = j+1; j = s.indexOf('/', i);
	    hd = Integer.parseInt(s.substring(i, j));

	    i = j+1; definitionString = s.substring(i, s.length());

	    if((wd > 0) && (hd > 0)) {
		definitionArea = new TextArea("", hd, wd, TextArea.SCROLLBARS_NONE);
		definitionArea.setEditable(false);
	    }
	}

	s = getParameter("content"); if(s == null) s = ".";
	grid = new Grid(f, sf, fm, sfm, w, h, s, definitionArea, definitionString);
	add("north", grid);

	s = getParameter("checkwordlabel");
	if(s != null) {
	    checkButton = new Button(s);
	    checkButton.addActionListener(this);
	}

	s = getParameter("givewordlabel");
	if(s != null) {
	    giveButton = new Button(s);
	    giveButton.addActionListener(this);
	}

	if((checkButton != null) || (giveButton != null)) {
	    Panel buttonPanel;
	    buttonPanel = new Panel();
	    if(checkButton != null) buttonPanel.add(checkButton);
	    if(giveButton != null) buttonPanel.add(giveButton);
	    if(definitionArea != null) add("south", definitionArea);
	    add("south", buttonPanel);
	}
    }

    public void actionPerformed(ActionEvent event) {
	Object source = event.getSource();
	if(source == checkButton) grid.checkWord();
	else if(source == giveButton) grid.giveWord();
    }

    public String getAppletInfo() {
	return "Word applet v1.5\n" +
	    "Written and (c) Francois Fleuret, mail to <francois.fleuret@inria.fr>\n" +
	    "Check http://www-rocq.inria.fr/~fleuret\n";
    }

    public String[][] getParameterInfo() {
	String chose[][] = {
	    { "bgcolor", "integer", "Background Color (optional)" },
	    { "font", "font name", "Grid character font name (optional)" },
	    { "fontsize", "integer", "Grid character font size (optional)" },
	    { "smallfont", "font name", "Row and column index font name (optional)" },
	    { "smallfontsize", "integer", "Row and column index font size (optional)" },
	    { "gridwidth", "integer", "Grid width" },
	    { "gridheight", "integer", "Grid height" },
	    { "content", "string", "Grid content" },
	    { "definitions", "string", "Definitions (optional)" },
	    { "checkwordlabel", "string", "Label for the 'check word' button (optional)" },
	    { "givewordlabel", "string", "Label for the 'give word' button (optional)" }
	};

	return chose;
    }
}


