/*
	SE3.3 Assignment 2
	Semester 2, 2004
	Brisbane North Institute of TAFE (Ithaca)

	Author: Ted Percival
	Date: 2004-11

	Licence: This code is in the public domain.
*/

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

class a2 extends JFrame {
	public a2() {
		super("Magic Square");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		getContentPane().add(new MainLayout());
		setSize(600,600);
		show();
	}
	
	public static void main(String args[]) {
		new a2();
	}
	
//-----	
	
	/*****
		The "game panel". Also includes "total" labels.
	*/
	class ButtonPanel extends JPanel implements ActionListener {
		public static final int DEFAULT_SIZE = 4; // default size to make the grid
		
		private GridLayout layout; // the playspace's layout
		private int size; // size of the playing field (not incl. totals)
		private ArrayList buttons; // clickable buttons
		private ArrayList horizTotals; // horizontal totals (along side)
		private ArrayList vertTotals; // vertical totals (along bottom)
		private JLabel fsDiagTotal, bsDiagTotal; // named as in "fs = foward-slash; bs = back-slash"
		
		// for the action handler
		private boolean firstButtonSelected = false;
		private MagicButton selectedButton; // the first-selected button
		
		/*****
			Constructor.
		*/
		public ButtonPanel() {
			super(new GridLayout(), true); // double-buffered
			layout = (GridLayout)getLayout();

			// initialise storage
			buttons = new ArrayList();
			horizTotals = new ArrayList();
			vertTotals = new ArrayList();
			fsDiagTotal = new JLabel("", JLabel.CENTER);
			bsDiagTotal = new JLabel("", JLabel.CENTER);
			
			setSize(DEFAULT_SIZE);
		}
		
		/*****
			Starts a new game using @size for the grid size.
		*/
		public void newGame(int size) {
			setSize(size);
			calculateTotals();
			
			// redraw - fixes a bug where starting a new game with the default size left the game panel visually blank
			validate();
		}
		
		/*****
			Calculates all the totals and updates them
		*/
		private void calculateTotals() {
			int total;

			// vertical
			for (int y = 0; y < size; ++y) {
				total = 0;
				for (int x = 0; x < size; ++x)
					total += getValueOfMb(buttons, x * size + y);
				((JLabel)vertTotals.get(y)).setText(Integer.toString(total));
			}
			
			// horizontal
			for (int x = 0; x < size; ++x) {
				total = 0;
				for (int y = 0; y < size; ++y)
					total += getValueOfMb(buttons, x * size + y);
				((JLabel)horizTotals.get(x)).setText(Integer.toString(total));
			}
			
			// diagonal
			// Top left -> bottom right (backslash)
			total = 0;
			for (int i = 0; i < size; ++i)
				total += getValueOfMb(buttons, i * size + i);
			bsDiagTotal.setText(Integer.toString(total));
			
			// Top right -> bottom left (forwardslash)
			// out by 4 over
			total = 0;
			for (int i = 0; i < size; ++i)
				total += getValueOfMb(buttons, (size * i) + (size - i) - 1);
			fsDiagTotal.setText(Integer.toString(total));
			
			// finally, check if the player has won the game
			checkTotals();
		}
		
		/*****
			Determines whether or not the user has won and acts accordingly.
			DEV: It would be nice if values weren't got by converting back from a string.
		*/
		private void checkTotals() {
			int total = Integer.parseInt(fsDiagTotal.getText());
			if (Integer.parseInt(bsDiagTotal.getText()) != total)
				return; // different.
			for (int i = 0; i < size; ++i) {
				if (total != Integer.parseInt(((JLabel)vertTotals.get(i)).getText()) // don't argue ;)
					|| total != Integer.parseInt(((JLabel)horizTotals.get(i)).getText()))
					return;
			}
			
			// all totals matched!
			int ret = JOptionPane.showConfirmDialog(
				this,
				"Congratulations, you won the game! Play again?",
				"Play again?",
				JOptionPane.YES_NO_OPTION);
			
			if (ret == JOptionPane.YES_OPTION)
				newGame(size);
			else
				System.exit(0);
		}
		
		/*****
			Provides a nicer method for determining a button's value.
		*/
		private int getValueOfMb(ArrayList list, int offset) {
			return ((MagicButton)list.get(offset)).number;
		}
		
		/*****
			Redraws the game panel where size is the number of selectable cells (not the total size)
		*/
		private void setSize(int size) {
			this.size = size;
			buttons.clear();

			// resize buttons vector
			buttons.ensureCapacity(size * size);
			
			removeAll();
			layout.setColumns(size+2);
			layout.setRows(size+1);
			
			for (int i = 0; i < size * size; ++i) {
				MagicButton btn = new MagicButton(i+1);
				btn.addActionListener(this);
				buttons.add(btn); // add to button ArrayList
			}
			
			for (int i = 0; i < size; ++i) {
				horizTotals.add(new JLabel("", JLabel.CENTER));
				vertTotals.add(new JLabel("", JLabel.CENTER));
			}
			
			redrawLayout();
		}
		
		/*****
			Draws all the buttons & labels onto the form. Usually only called at the beginning of a game
		*/
		public void redrawLayout() {
			for (int y = 0; y < size; ++y) {
				add(new JLabel()); // blank
				for (int x = 0; x < size; ++x) {
					add((Component)buttons.get(y * size + x));
				}
				// horizontal totals
				add((Component)horizTotals.get(y));
			}
			// forwardslash
			add(fsDiagTotal);
			
			// bottom totals
			for (int i = 0; i < size; ++i)
				add((Component)vertTotals.get(i));
			
			// backslash
			add(bsDiagTotal);
			
			calculateTotals();
		}
		
		/*****
			A button usable on the game panel.
			Provides slightly faster access to its numeric value than reading from its label.
		*/
		class MagicButton extends Button {
			public int number;
			/*****
				Constructor
			*/
			public MagicButton(int number) {
				setValue(number);
			}
		
			/*****
				Sets the button's number.
			*/
			public void setValue(int value) {
				number = value;
				setLabel(Integer.toString(value));
			}
		}
		
		/*****
			Processes actions for game panel buttons.
			This is the "guts" of the game when it is in play.
		*/
		public void actionPerformed(ActionEvent event) {
			if (!firstButtonSelected) {
				// disable first button
				selectedButton = (MagicButton)event.getSource();
				selectedButton.setEnabled(false);
				firstButtonSelected = true;
				return;
			}	
			
			// else the first button has already been selected; 2nd has now been activated
			
			// find the buttons!
			for (int i = 0; i < buttons.size(); ++i) {
				if (buttons.get(i) == event.getSource()) {
					// found the button, break from the loop
					break;
				}
			}
			
			// swap them
			int swapvalue;
			swapvalue = selectedButton.number;
			selectedButton.setValue(((MagicButton)event.getSource()).number);
			((MagicButton)event.getSource()).setValue(swapvalue);
			
			// reenable the disabled button.
			// disable this for a more challenging game!
			selectedButton.setEnabled(true);
			
			firstButtonSelected = false;
			
			// Update totals
			calculateTotals();
		}
		
	}

//-----
	
	class TopPanel extends JPanel {
		Object owner;
		
		public TopLeftPanel topLeftPanel;
		
		TopPanel(Object owner) {
			super(new GridLayout(1, 2, 0, 0));
			this.owner = owner;
			topLeftPanel = new TopLeftPanel();
			add(topLeftPanel);
			add(new TopRightPanel());
		}
		
		class TopLeftPanel extends JPanel {
			public JTextField txtGridSize;
			TopLeftPanel() {
				super(new FlowLayout(FlowLayout.LEFT));
				txtGridSize = new JTextField(Integer.toString(ButtonPanel.DEFAULT_SIZE), 2);
				add(new JLabel("Grid size:"));
				add(txtGridSize);
			}
		}
		
		class TopRightPanel extends JPanel implements ActionListener {
			JButton btnNewGame;
			
			TopRightPanel() {
				super(new FlowLayout(FlowLayout.RIGHT));
				
				btnNewGame = new JButton("New Game");
				btnNewGame.addActionListener(this);
				add(btnNewGame);
			}
			
			// This action handler ONLY handles for the "New Game" button.
			public void actionPerformed(ActionEvent e) {
				
				int gameSize = Integer.parseInt(((MainLayout)owner).topPanel.topLeftPanel.txtGridSize.getText());
				
				if (gameSize < 1) {
					JOptionPane.showMessageDialog(this, "Game size must be 1 or more. 4 is recommended.", "Invalid game size", JOptionPane.ERROR_MESSAGE);
					return;	
				}
				
				((MainLayout)owner).newGame(gameSize);
			}		
		}
		
	}

//-----

	/*****
		The program.
	*/
	class MainLayout extends JPanel {
		private ButtonPanel buttonPanel;
		public TopPanel topPanel;
		MainLayout() {
			super(new BorderLayout());
			buttonPanel = new ButtonPanel();
			topPanel = new TopPanel(this);

			add(topPanel, BorderLayout.NORTH);
			add(buttonPanel, BorderLayout.CENTER);
		}
		
		/*****
			Begins a new game.
		*/
		public void newGame(int size) {
			buttonPanel.newGame(size);
		}
	}
}
