/**
 * Ataxx - A GUI interface for playing the game Ataxx.  
 * Depends on and is useful for testing class <code>AtaxxState</code>.
 * Red/blue pieces correspond to the 1st/2nd player, respectively.
 *
 * @author Todd Neller
 * @version 1.0

Copyright (C) 2014 Todd Neller

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.

Information about the GNU General Public License is available online at:
  http://www.gnu.org/licenses/
To receive a copy of the GNU General Public License, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.

 */

import java.awt.*;
import java.awt.event.*;
import java.util.Stack;

import javax.swing.*;

public class Ataxx extends JFrame {
	private static final long serialVersionUID = -3559314834239139234L;

	public Ataxx() {
		super();
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setTitle("Ataxx");
		add(new AtaxxPanel(), BorderLayout.CENTER);
		pack();
		setVisible(true);
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Ataxx();
	}
}

class AtaxxPanel extends JPanel {
	private static final long serialVersionUID = -2855922192345269091L;
	private static final boolean flipBoard = true, reportMoves = true, undoEnabled = true;
	boolean isMovingPiece = false; // whether or not piece is currently being moved (i.e. dragged)
	int pressRow, pressCol; // row, col of mouse press
	int mouseX, mouseY; // current position of dragging mouse
	static final int SQUARE1 = 0, SQUARE2 = 1, PLAYER1 = 2, PLAYER2 = 3, IMAGE_SIZE = 80, SIZE = AtaxxState.SIZE;
	static final String[] IMAGE_FILENAMES = {"whiteMarble80.gif", "greenMarble80.gif", "red80.gif", "blue80.gif"};
	Image[] images = new Image[4]; 
	AtaxxState state = new AtaxxState();
	Stack<AtaxxState> stateStack = new Stack<AtaxxState>();

	AtaxxPanel() {
		// initialize panel
		setBackground(Color.white);
		setPreferredSize(new Dimension(SIZE * IMAGE_SIZE, SIZE * IMAGE_SIZE));
		setSize(SIZE * IMAGE_SIZE, SIZE * IMAGE_SIZE);
		addMouseListener(new MouseHandler());
		addMouseMotionListener(new MouseMotionHandler());
		if (undoEnabled)
			addKeyListener(new UndoHandler());
		requestFocusInWindow();

		// load images
		for (int i = 0; i < IMAGE_FILENAMES.length; i++)
			images[i] = getToolkit().getImage(IMAGE_FILENAMES[i]);
		MediaTracker mt = new MediaTracker(this);
		for (int i = 0; i < IMAGE_FILENAMES.length; i++)
			mt.addImage(images[i], i);
		try {
			mt.waitForAll();
		} catch (Exception e) {
			System.err.println("Exception while loading image.");
		}
		// check that images loaded
		for (int i = 0; i < IMAGE_FILENAMES.length; i++)
			if (images[i].getWidth(this) == 0) {
				System.err.printf("Image \"%s\" not loaded.\n", IMAGE_FILENAMES[i]);
				System.exit(0);
			}
	}

	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		// draw each board square
		for (int row = 0; row < AtaxxState.SIZE; row++)
			for (int col = 0; col < AtaxxState.SIZE; col++) {
				int y = IMAGE_SIZE * (flipBoard ? AtaxxState.SIZE - 1 - row : row);
				int x = IMAGE_SIZE * col;
				g2.drawImage(images[((row + col) % 2 == 0) ? SQUARE1 : SQUARE2], x, y, this); // draw background checkerboard square
				int contents = state.getPiece(row, col);
				// draw square contents (if any)

				if (contents == AtaxxState.RED && (!isMovingPiece || pressRow != row || pressCol != col)) // if red piece that's not in motion
					g2.drawImage(images[PLAYER1], x, y, this); // draw red piece
				else if (contents == AtaxxState.BLUE && (!isMovingPiece || pressRow != row || pressCol != col)) // if blue piece that's not in motion
					g2.drawImage(images[PLAYER2], x, y, this); // draw blue piece
			}
		if (isMovingPiece) // if piece is moving
			g2.drawImage(images[state.getPiece(pressRow, pressCol) == AtaxxState.RED ? PLAYER1 : PLAYER2], 
					mouseX - IMAGE_SIZE / 2, mouseY - IMAGE_SIZE / 2, this); // draw piece
	}

	// a mouse handler to trigger mouse press/release events
	class MouseHandler extends MouseAdapter {

		@Override
		public void mousePressed(MouseEvent event) {
			requestFocusInWindow();
			pressRow = event.getY() / AtaxxPanel.IMAGE_SIZE; // discretize source board row, col
			pressCol = event.getX() / AtaxxPanel.IMAGE_SIZE;
			if (flipBoard)
				pressRow = AtaxxState.SIZE - pressRow - 1;
			if (state.getPiece(pressRow, pressCol) == AtaxxState.RED || state.getPiece(pressRow, pressCol) == AtaxxState.BLUE)
				isMovingPiece = true; // change state to reflect piece motion potential
		}

		@Override
		public void mouseReleased(MouseEvent event) {
			requestFocusInWindow();
			isMovingPiece = false; // change state to reflect piece motion impossibility
			int releaseRow = event.getY() / AtaxxPanel.IMAGE_SIZE; // discretize destination row, col
			int releaseCol = event.getX() / AtaxxPanel.IMAGE_SIZE;
			if (flipBoard)
				releaseRow = AtaxxState.SIZE - releaseRow - 1;
			// move
			if (reportMoves) {
				//				//System.out.printf("%d %d %d %d\n", pressRow, pressCol, releaseRow, releaseCol);
				if (pressRow == releaseRow && pressCol == releaseCol)
					System.out.println((char) ('a' + releaseCol) + Integer.toString(releaseRow + 1));
				else
					System.out.println((char) ('a' + pressCol) + Integer.toString(pressRow + 1) + " " + (char) ('a' + releaseCol) + Integer.toString(releaseRow + 1));
			}
			AtaxxState copy = (AtaxxState) state.clone();
			if (pressRow == releaseRow && pressCol == releaseCol) { // growth move
				if (state.grow(releaseRow, releaseCol)) { //attempt growth, checking legality
					stateStack.push(copy); 
				}
			}
			else if (state.jump(pressRow, pressCol, releaseRow, releaseCol)) { // attempt move, checking legality
				stateStack.push(copy); // push copy for undo
			}	
			repaint(); // repaint
			if (state.getWinner() != AtaxxState.NONE) {
				int score1 = 0, score2 = 0;
				for (int row = 0; row < AtaxxState.SIZE; row++)
					for (int col = 0; col < AtaxxState.SIZE; col++)
						if (state.getPiece(row, col) == AtaxxState.RED)
							score1++;
						else
							score2++;
				JOptionPane.showMessageDialog(null, String.format("%s wins %d to %d!\n", state.getWinner() == AtaxxState.BLUE ? "Blue" : "Red", Math.max(score1, score2), Math.min(score1, score2)));
			}
		}
	}


	// a mouse motion handler to track piece movement and force repainting during movement
	// based on example from http://www.java2s.com/Code/Java/2D-Graphics-GUI/Imagewithmousedragandmoveevent.htm
	class MouseMotionHandler extends MouseMotionAdapter {
		public void mouseDragged(MouseEvent e) {
			mouseX = e.getX(); // track piece movement in mouseX, mouseY fields
			mouseY = e.getY();
			repaint(); // force repaint to animate motion
		}
	}

	// a keypress listener that listens for "u" undo commands
	class UndoHandler extends KeyAdapter {
		public void keyTyped(KeyEvent e) {
			if (reportMoves)
				System.out.println("undo");
			if (e.getKeyChar() == 'u') {
				if (stateStack.isEmpty()) 
					JOptionPane.showMessageDialog(null, "Cannot undo.", "Error", JOptionPane.ERROR_MESSAGE);
				else {
					state = stateStack.pop();
					repaint();
				}
			}
		}
	}

}
