package jrummikub.view.impl;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import jrummikub.model.GameSettings;
import jrummikub.model.PlayerSettings;
import jrummikub.model.PlayerSettings.Type;
import jrummikub.model.StoneColor;
import jrummikub.util.Event;
import jrummikub.util.Event1;
import jrummikub.util.Event2;
import jrummikub.util.IEvent;
import jrummikub.util.IEvent1;
import jrummikub.util.IEvent2;
import jrummikub.view.ISettingsPanel;

@SuppressWarnings("serial")
class SettingsPanel extends JPanel implements ISettingsPanel {
	private List<List<Type>> playerTypeChoices = Collections.emptyList();
	private List<Boolean> removeButtonsEnabled = Collections.emptyList();
	private List<Boolean> playerNamesEditable = Collections.emptyList();

	private Set<Color> playerColors;

	private SettingsMode settingsMode = SettingsMode.DEFAULT;

	private JTabbedPane tabbedPane;
	private JPanel playerSetupPanel;
	private JPanel playerSettingsViewport;
	private JPanel optionsPanel;
	private JButton addPlayerButton;
	private JLabel errorMessageLabel;
	private JButton startButton;
	private JButton offerButton;

	private List<PlayerSettingsPanel> playerSettingsPanels = new ArrayList<PlayerSettingsPanel>();

	private JLabel variantLabel;
	private JPanel variantPanel;

	private JButton setVariantDefaultButton;
	private JButton setVariantChildrenButton;

	private JSpinner initialMeldThresholdSpinner;
	private JSpinner stoneSetNumberSpinner;
	private JSpinner highestValueSpinner;
	private JSpinner numberOfStonesDealtSpinner;
	private JSpinner jokerNumberSpinner;
	private JSpinner timeSpinner;
	private JCheckBox noLimitsBox;
	private JPanel colorSelectionPanel;
	private Map<StoneColor, JToggleButton> colorButtons = new HashMap<StoneColor, JToggleButton>();
	private JPanel colorLabelPanel;

	private JLabel initialMeldThresholdLabel;
	private JLabel stoneSetNumberLabel;
	private JLabel highestValueLabel;
	private JLabel numberOfStonesDealtLabel;
	private JLabel jokerNumberLabel;
	private JLabel timeLabel;
	private JLabel noLimitsLabel;

	private JButton loadButton;
	private JButton networkButton;
	private JButton backButton;
	private JPanel buttonPanel;

	private Event startGameEvent = new Event();
	private Event loadGameEvent = new Event();
	private Event networkGameEvent = new Event();
	private Event offerGameEvent = new Event();
	private Event backEvent = new Event();
	private Event addPlayerEvent = new Event();
	private Event1<Integer> removePlayerEvent = new Event1<Integer>();
	private Event2<Integer, Color> changePlayerColorEvent = new Event2<Integer, Color>();
	private Event2<Integer, String> changePlayerNameEvent = new Event2<Integer, String>();
	private Event2<Integer, Type> changePlayerTypeEvent = new Event2<Integer, Type>();
	private Event1<Integer> changeInitialMeldThresholdEvent = new Event1<Integer>();
	private Event1<Integer> changeJokerNumberEvent = new Event1<Integer>();
	private Event1<Integer> changeStoneSetNumberEvent = new Event1<Integer>();
	private Event1<Integer> changeNumberOfStonesDealtEvent = new Event1<Integer>();
	private Event1<Integer> changeHighestValueEvent = new Event1<Integer>();
	private Event1<Set<StoneColor>> changeStoneColorsEvent = new Event1<Set<StoneColor>>();
	private Event1<Integer> changeTimeEvent = new Event1<Integer>();
	private Event1<Boolean> changeNoLimitsEvent = new Event1<Boolean>();
	private Event setVariantDefaultEvent = new Event();
	private Event setVariantChildrenEvent = new Event();

	@Override
	public IEvent getStartGameEvent() {
		return startGameEvent;
	}

	@Override
	public IEvent getOfferGameEvent() {
		return offerGameEvent;
	}

	IEvent getLoadGameEvent() {
		return loadGameEvent;
	}

	IEvent getNetworkGameEvent() {
		return networkGameEvent;
	}

	@Override
	public IEvent getAddPlayerEvent() {
		return addPlayerEvent;
	}

	@Override
	public IEvent1<Integer> getRemovePlayerEvent() {
		return removePlayerEvent;
	}

	@Override
	public IEvent2<Integer, Color> getChangePlayerColorEvent() {
		return changePlayerColorEvent;
	}

	@Override
	public IEvent2<Integer, String> getChangePlayerNameEvent() {
		return changePlayerNameEvent;
	}

	@Override
	public IEvent1<Integer> getChangeInitialMeldThresholdEvent() {
		return changeInitialMeldThresholdEvent;
	}

	@Override
	public IEvent1<Integer> getChangeStoneSetNumberEvent() {
		return changeStoneSetNumberEvent;
	}

	@Override
	public IEvent1<Integer> getChangeNumberOfStonesDealtEvent() {
		return changeNumberOfStonesDealtEvent;
	}

	@Override
	public IEvent1<Integer> getChangeHighestValueEvent() {
		return changeHighestValueEvent;
	}

	@Override
	public IEvent1<Integer> getChangeJokerNumberEvent() {
		return changeJokerNumberEvent;
	}

	@Override
	public IEvent2<Integer, Type> getChangePlayerTypeEvent() {
		return changePlayerTypeEvent;
	}

	@Override
	public IEvent1<Integer> getChangeTimeEvent() {
		return changeTimeEvent;
	}

	@Override
	public IEvent1<Boolean> getChangeNoLimitsEvent() {
		return changeNoLimitsEvent;
	}

	@Override
	public IEvent1<Set<StoneColor>> getChangeStoneColorsEvent() {
		return changeStoneColorsEvent;
	}

	@Override
	public IEvent getSetVariantDefaultEvent() {
		return setVariantDefaultEvent;
	}

	@Override
	public IEvent getSetVariantChildrenEvent() {
		return setVariantChildrenEvent;
	}

	@Override
	public IEvent getBackEvent() {
		return backEvent;
	}

	@Override
	public void setPlayerTypeChoices(List<List<Type>> choices) {
		playerTypeChoices = choices;
	}

	@Override
	public void setPlayerColors(Set<Color> colors) {
		playerColors = colors;
	}

	@Override
	public void setError(SettingsError error) {
		switch (error) {
		case NO_ERROR:
			errorMessageLabel.setText(" ");
			break;
		case DUPLICATE_PLAYER_NAME_ERROR:
			errorMessageLabel
					.setText("Jeder Spielername darf nur einmal verwendet werden.");
			errorMessageLabel.setForeground(Color.RED);
			break;
		case NO_PLAYER_NAME_ERROR:
			errorMessageLabel.setText("Jeder Spieler muss einen Namen haben.");
			errorMessageLabel.setForeground(Color.RED);
			break;
		case NOT_ENOUGH_STONES_ERROR:
			errorMessageLabel
					.setText("Es gibt nicht genug Steine f\u00fcr die gew\u00e4hlte Spieleranzahl.");
			errorMessageLabel.setForeground(Color.RED);
			break;
		case NOT_ENOUGH_COLORS_ERROR:
			errorMessageLabel
					.setText("Es m\u00fcssen mindestens drei Farben ausgew\u00e4hlt werden.");
			errorMessageLabel.setForeground(Color.RED);
			break;
		case COMPUTER_PLAYERS_ONLY_WARNING:
			errorMessageLabel.setText("Es gibt keinen menschlichen Spieler.");
			errorMessageLabel.setForeground(Color.ORANGE.darker());
			break;
		case TOO_HIGH_THRESHOLD_WARNING:
			errorMessageLabel
					.setText("Die gew\u00e4hlte Schranke ist m\u00f6glicherweise zu hoch");
			errorMessageLabel.setForeground(Color.ORANGE.darker());
			break;
		}

	}

	@Override
	public void enableStartGameButton(boolean enable) {
		startButton.setEnabled(enable);
		offerButton.setEnabled(enable);
	}

	@Override
	public void enableAddPlayerButton(boolean enable) {
		addPlayerButton.setVisible(enable);
	}

	@Override
	public void enableRemovePlayerButtons(List<Boolean> enable) {
		removeButtonsEnabled = enable;
		Iterator<Boolean> it = enable.iterator();
		for (PlayerSettingsPanel psp : playerSettingsPanels) {
			if (!it.hasNext()) {
				psp.enableRemoveButton(false);
			} else {
				psp.enableRemoveButton(it.next());
			}
		}
	}

	@Override
	public void setPlayerNamesEditable(List<Boolean> editable) {
		playerNamesEditable = editable;

		Iterator<Boolean> it = editable.iterator();
		for (PlayerSettingsPanel psp : playerSettingsPanels) {
			if (!it.hasNext()) {
				psp.setPlayerNameEditable(true);
			} else {
				psp.setPlayerNameEditable(it.next());
			}
		}
	}

	@Override
	public void setGameSettings(GameSettings gameSettings) {
		while (playerSettingsPanels.size() > gameSettings.getPlayerList().size()) {
			removePlayerSettingsPanel();
		}

		for (int i = 0; i < gameSettings.getPlayerList().size(); ++i) {
			List<Type> choices = Collections.emptyList();
			if (i < playerTypeChoices.size()) {
				choices = playerTypeChoices.get(i);
			}
			updatePlayerSettingsPanel(i, gameSettings.getPlayerList().get(i), choices);
		}

		setInitialSettingsValue(gameSettings);

		initialMeldThresholdLabel.setText(gameSettings.getInitialMeldThreshold()
				+ " Punkte");
		stoneSetNumberLabel.setText(Integer.toString(gameSettings
				.getStoneSetNumber()));
		highestValueLabel.setText(Integer.toString(gameSettings.getHighestValue()));
		numberOfStonesDealtLabel.setText(Integer.toString(gameSettings
				.getNumberOfStonesDealt()));
		jokerNumberLabel.setText(Integer.toString(gameSettings.getJokerNumber()));
		timeLabel.setText(gameSettings.getTotalTime() + " Sekunden");
		noLimitsLabel.setText(gameSettings.isNoLimits() ? "An" : "Aus");

		colorLabelPanel.removeAll();

		for (StoneColor color : StoneColor.values()) {
			boolean contained = gameSettings.getStoneColors().contains(color);

			colorButtons.get(color).getModel().setSelected(contained);

			if (contained) {
				colorLabelPanel.add(new JLabel(ImageUtil.createColorIcon(
						StonePainter.getColor(color), 16, 1)));
			}
		}

		playerSettingsViewport.revalidate();
	}

	private void setInitialSettingsValue(GameSettings gameSettings) {
		initialMeldThresholdSpinner
				.setValue(gameSettings.getInitialMeldThreshold());
		stoneSetNumberSpinner.setValue(gameSettings.getStoneSetNumber());
		highestValueSpinner.setValue(gameSettings.getHighestValue());
		numberOfStonesDealtSpinner.setValue(gameSettings.getNumberOfStonesDealt());
		jokerNumberSpinner.setValue(gameSettings.getJokerNumber());
		timeSpinner.setValue(gameSettings.getTotalTime());
		noLimitsBox.setSelected(gameSettings.isNoLimits());
	}

	private void updatePlayerSettingsPanel(int i, PlayerSettings settings,
			List<Type> choices) {
		if (i < playerSettingsPanels.size()) {
			playerSettingsPanels.get(i).setChoices(choices);
			playerSettingsPanels.get(i).setSettings(settings);
		} else {
			addPlayerSettingsPanel(i, settings, choices);
		}
	}

	private void addPlayerSettingsPanel(int i, PlayerSettings settings,
			List<Type> choices) {
		PlayerSettingsPanel panel = new PlayerSettingsPanel(i, settings, choices,
				settingsMode != SettingsMode.NETWORK_OFFER
						&& settingsMode != SettingsMode.NETWORK_JOIN);
		playerSettingsPanels.add(panel);
		playerSettingsViewport.add(panel,
				playerSettingsViewport.getComponentCount() - 1);
		enableRemovePlayerButtons(removeButtonsEnabled);
		setPlayerNamesEditable(playerNamesEditable);
	}

	private void removePlayerSettingsPanel() {
		PlayerSettingsPanel p = playerSettingsPanels.remove(playerSettingsPanels
				.size() - 1);
		playerSettingsViewport.remove(p);
	}

	private void createPlayerSetupPanel() {
		playerSetupPanel = new JPanel();
		playerSetupPanel.setLayout(new BorderLayout());

		playerSettingsViewport = new JPanel();
		playerSettingsViewport.setLayout(new BoxLayout(playerSettingsViewport,
				BoxLayout.Y_AXIS));

		JScrollPane scrollPane = new JScrollPane(playerSettingsViewport,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		playerSetupPanel.add(scrollPane);

		JPanel addPlayerPanel = new JPanel();
		addPlayerPanel.setLayout(new FlowLayout(FlowLayout.TRAILING, 0, 2));
		playerSettingsViewport.add(addPlayerPanel);

		addPlayerButton = new JButton("+");
		addPlayerButton.setFont(addPlayerButton.getFont().deriveFont(Font.BOLD));
		addPlayerButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				addPlayerEvent.emit();
			}
		});
		addPlayerPanel.add(addPlayerButton);
	}

	private JComponent makeFiller() {
		return new Box.Filler(new Dimension(0, 0), new Dimension(0, 0),
				new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
	}

	private void createOptionsPanel() {
		optionsPanel = new JPanel();
		GridBagLayout layout = new GridBagLayout();
		optionsPanel.setLayout(layout);

		int row = 1;

		variantLabel = new JLabel("Variante:");
		makeVariantPanel();

		row = makeAllOptionLabels(row);

		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 1;
		c.gridy = row;
		c.fill = GridBagConstraints.BOTH;
		c.weightx = 1;
		c.weighty = 1;
		optionsPanel.add(makeFiller(), c);
	}

	private int makeAllOptionLabels(int row) {
		makeOptionLabel(row++, "Auslegeschranke:");
		initialMeldThresholdLabel = new JLabel();
		initialMeldThresholdSpinner = createOptionSpinner(1, 999, 1,
				changeInitialMeldThresholdEvent);

		makeOptionLabel(row++, "Anzahl Steins\u00e4tze:");
		stoneSetNumberLabel = new JLabel();
		stoneSetNumberSpinner = createOptionSpinner(1, 999, 1,
				changeStoneSetNumberEvent);

		makeOptionLabel(row++, "H\u00f6chster Steinwert:");
		highestValueLabel = new JLabel();
		highestValueSpinner = createOptionSpinner(3, 99, 1, changeHighestValueEvent);

		makeOptionLabel(row++, "Anzahl Startsteine:");
		numberOfStonesDealtLabel = new JLabel();
		numberOfStonesDealtSpinner = createOptionSpinner(1, 999, 1,
				changeNumberOfStonesDealtEvent);

		makeOptionLabel(row++, "Jokeranzahl:");
		jokerNumberLabel = new JLabel();
		jokerNumberSpinner = createOptionSpinner(0, 999, 1, changeJokerNumberEvent);

		makeOptionLabel(row++, "Zeit f\u00fcr Spielzug:");
		timeLabel = new JLabel();
		timeSpinner = createOptionSpinner(1, 999, 1, changeTimeEvent);

		makeOptionLabel(row++, "No Limits:");
		noLimitsLabel = new JLabel();
		noLimitsBox = createOptionCheckbox(changeNoLimitsEvent);

		makeOptionLabel(row, "Steinfarben:");
		createColorSelectionPanel();
		colorLabelPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
		return row;
	}

	private void removeOptionPanelComponentsAndLabels() {
		optionsPanel.remove(variantLabel);
		optionsPanel.remove(variantPanel);

		optionsPanel.remove(initialMeldThresholdSpinner);
		optionsPanel.remove(initialMeldThresholdLabel);
		optionsPanel.remove(stoneSetNumberSpinner);
		optionsPanel.remove(stoneSetNumberLabel);
		optionsPanel.remove(highestValueSpinner);
		optionsPanel.remove(highestValueLabel);
		optionsPanel.remove(numberOfStonesDealtSpinner);
		optionsPanel.remove(numberOfStonesDealtLabel);
		optionsPanel.remove(jokerNumberSpinner);
		optionsPanel.remove(jokerNumberLabel);
		optionsPanel.remove(timeSpinner);
		optionsPanel.remove(timeLabel);
		optionsPanel.remove(noLimitsBox);
		optionsPanel.remove(noLimitsLabel);

		optionsPanel.remove(colorSelectionPanel);
		optionsPanel.remove(colorLabelPanel);
	}

	private void addOptionPanelComponents() {
		removeOptionPanelComponentsAndLabels();

		int row = 0;
		addOptionPanelComponent(variantLabel, true, row);
		addOptionPanelComponent(variantPanel, false, row++);
		addOptionPanelComponent(initialMeldThresholdSpinner, false, row++);
		addOptionPanelComponent(stoneSetNumberSpinner, false, row++);
		addOptionPanelComponent(highestValueSpinner, false, row++);
		addOptionPanelComponent(numberOfStonesDealtSpinner, false, row++);
		addOptionPanelComponent(jokerNumberSpinner, false, row++);
		addOptionPanelComponent(timeSpinner, false, row++);
		addOptionPanelComponent(noLimitsBox, false, row++);
		addOptionPanelComponent(colorSelectionPanel, false, row++);
	}

	private void addOptionPanelLabels() {
		removeOptionPanelComponentsAndLabels();

		int row = 1;
		addOptionPanelComponent(initialMeldThresholdLabel, false, row++);
		addOptionPanelComponent(stoneSetNumberLabel, false, row++);
		addOptionPanelComponent(highestValueLabel, false, row++);
		addOptionPanelComponent(numberOfStonesDealtLabel, false, row++);
		addOptionPanelComponent(jokerNumberLabel, false, row++);
		addOptionPanelComponent(timeLabel, false, row++);
		addOptionPanelComponent(noLimitsLabel, false, row++);
		addOptionPanelComponent(colorLabelPanel, false, row++);
	}

	private void addOptionPanelComponent(JComponent component, boolean label,
			int row) {
		GridBagConstraints c = new GridBagConstraints();
		c.anchor = label ? GridBagConstraints.EAST : GridBagConstraints.WEST;
		c.gridx = label ? 0 : 1;
		c.gridy = row;
		c.insets = new Insets(2, 2, 2, 2);

		optionsPanel.add(component, c);
	}

	private void createColorSelectionPanel() {
		colorSelectionPanel = new JPanel();

		for (StoneColor color : StoneColor.values()) {
			createColorButton(color);
		}
	}

	private void createColorButton(final StoneColor color) {
		JToggleButton button = new JToggleButton(ImageUtil.createColorIcon(
				StonePainter.getColor(color), 12, 1));
		button.setMargin(new Insets(3, 3, 3, 3));
		button.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged(ChangeEvent e) {
				emitColorChangeEvent();
			}
		});

		colorSelectionPanel.add(button);

		colorButtons.put(color, button);
	}

	private void emitColorChangeEvent() {
		Set<StoneColor> colors = new HashSet<StoneColor>();

		for (StoneColor color : StoneColor.values()) {
			if (colorButtons.get(color).getModel().isSelected()) {
				colors.add(color);
			}
		}

		changeStoneColorsEvent.emit(colors);
	}

	private void makeVariantPanel() {
		variantPanel = new JPanel(new GridLayout(1, 2, 5, 0));

		setVariantDefaultButton = new JButton("Standard");
		variantPanel.add(setVariantDefaultButton);
		setVariantDefaultButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				setVariantDefaultEvent.emit();
			}
		});
		setVariantChildrenButton = new JButton("Kinder");
		variantPanel.add(setVariantChildrenButton);
		setVariantChildrenButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				setVariantChildrenEvent.emit();
			}
		});
	}

	private JCheckBox createOptionCheckbox(final Event1<Boolean> targetEvent) {
		final JCheckBox box = new JCheckBox();
		box.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				targetEvent.emit(box.isSelected());
			}
		});
		return box;
	}

	private JSpinner createOptionSpinner(int min, int max, int step,
			final Event1<Integer> targetEvent) {
		final JSpinner spinner = new JSpinner();
		spinner.setModel(new SpinnerNumberModel(min, min, max, step));
		spinner
				.setPreferredSize(new Dimension(60, spinner.getMinimumSize().height));
		spinner.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				targetEvent.emit((Integer) spinner.getValue());
			}
		});
		return spinner;
	}

	private JLabel makeOptionLabel(int row, String name) {
		GridBagConstraints c = new GridBagConstraints();
		c.anchor = GridBagConstraints.EAST;
		c.gridx = 0;
		c.gridy = row;
		c.insets = new Insets(4, 4, 4, 4);

		JLabel label = new JLabel(name);
		optionsPanel.add(label, c);

		return label;
	}

	SettingsPanel() {
		setLayout(new GridBagLayout());

		tabbedPane = new JTabbedPane();

		createPlayerSetupPanel();
		tabbedPane.addTab("Spieler", playerSetupPanel);

		createOptionsPanel();
		JScrollPane scrollPane = new JScrollPane(optionsPanel,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		tabbedPane.addTab("Optionen", scrollPane);

		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = GridBagConstraints.REMAINDER;
		c.weightx = 1;
		c.weighty = 1;
		add(tabbedPane, c);

		errorMessageLabel = new JLabel(" ");
		c.weighty = 0;
		add(errorMessageLabel, c);

		buttonPanel = new JPanel();
		buttonPanel.setLayout(new GridBagLayout());
		add(buttonPanel, c);

		startButton = createButton("Spiel starten", startGameEvent);
		loadButton = createButton("Spiel laden...", loadGameEvent);
		networkButton = createButton("Netzwerkspiel...", networkGameEvent);

		offerButton = createButton("Spiel anbieten", offerGameEvent);
		backButton = createButton("Zur\u00fcck", backEvent);

		addDefaultButtons();

		setBorder(new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder(
				10, 10, 10, 10)));
	}

	void resetTabbedPane() {
		tabbedPane.setSelectedIndex(0);
	}

	private void addDefaultButtons() {
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = 1;
		c.weightx = 1;
		c.weighty = 1;

		buttonPanel.add(startButton, c);
		c.weightx = 0;
		buttonPanel.add(Box.createHorizontalStrut(10), c);
		c.weightx = 1;
		buttonPanel.add(loadButton, c);
		c.weightx = 0;
		buttonPanel.add(Box.createHorizontalStrut(10), c);
		c.weightx = 1;
		c.gridwidth = GridBagConstraints.REMAINDER;
		buttonPanel.add(networkButton, c);
	}

	private void addNetworkSetupButtons() {
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = 1;
		c.weightx = 1;
		c.weighty = 1;

		buttonPanel.add(offerButton, c);
		c.weightx = 0;
		buttonPanel.add(Box.createHorizontalStrut(10), c);
		c.weightx = 1;
		c.gridwidth = GridBagConstraints.REMAINDER;
		buttonPanel.add(backButton, c);
	}

	private void addNetworkOfferButtons() {
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = 1;
		c.weightx = 1;
		c.weighty = 1;

		buttonPanel.add(startButton, c);
		c.weightx = 0;
		buttonPanel.add(Box.createHorizontalStrut(10), c);
		c.weightx = 1;
		c.gridwidth = GridBagConstraints.REMAINDER;
		buttonPanel.add(backButton, c);
	}

	private void addNetworkJoinButtons() {
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = GridBagConstraints.REMAINDER;
		c.weightx = 1;
		c.weighty = 1;

		buttonPanel.add(backButton, c);
	}

	private JButton createButton(String title, final Event event) {
		JButton button = new JButton(title);
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				event.emit();
			}
		});
		return button;
	}

	@Override
	public void setSettingsMode(SettingsMode mode) {
		settingsMode = mode;

		while (!playerSettingsPanels.isEmpty()) {
			removePlayerSettingsPanel();
		}

		buttonPanel.removeAll();

		switch (mode) {
		case DEFAULT:
			addDefaultButtons();
			enableOptions(true);
			break;
		case NETWORK_SETUP:
			addNetworkSetupButtons();
			enableOptions(true);
			break;
		case NETWORK_OFFER:
			addNetworkOfferButtons();
			enableOptions(false);
			break;
		case NETWORK_JOIN:
			addNetworkJoinButtons();
			enableOptions(false);
			break;
		}
	}

	private void enableOptions(boolean enable) {
		if (enable) {
			addOptionPanelComponents();
		} else {
			addOptionPanelLabels();
		}
	}

	private class PlayerSettingsPanel extends JPanel {
		private int playerNumber;
		private JButton colorButton;
		private JTextField nameField;
		private JButton removeButton;
		private JComboBox playerType;
		private List<Type> playerTypeChoices;
		private boolean colorChangable = true;

		private JLabel nameLabel;
		private JLabel typeLabel;

		public void setSettings(PlayerSettings settings) {
			if (nameField != null && !nameField.getText().equals(settings.getName())) {
				nameField.setText(settings.getName());
			}
			if (nameLabel != null) {
				nameLabel.setText(settings.getName());
			}

			if (playerType != null) {
				int index = playerTypeChoices.indexOf(settings.getType());
				if (index != playerType.getSelectedIndex() && index >= 0
						&& index < playerType.getModel().getSize()) {
					playerType.setSelectedIndex(index);
				}
			}
			if (typeLabel != null) {
				typeLabel.setText(getTypeLabel(settings.getType()));
			}
			colorButton
					.setIcon(ImageUtil.createColorIcon(settings.getColor(), 16, 2));

			colorChangable = !((settingsMode == SettingsMode.NETWORK_JOIN | settingsMode == SettingsMode.NETWORK_OFFER) & settings
					.getType() != Type.HUMAN);
		}

		public void setPlayerNameEditable(boolean editable) {
			if (nameField != null) {
				nameField.setEditable(editable);
			}
		}

		public void enableRemoveButton(boolean enable) {
			if (removeButton != null) {
				removeButton.setEnabled(enable);
			}
		}

		class NameChangeListener implements DocumentListener {
			@Override
			public void insertUpdate(DocumentEvent e) {
				changeName();
			}

			@Override
			public void removeUpdate(DocumentEvent e) {
				changeName();
			}

			@Override
			public void changedUpdate(DocumentEvent e) {
				changeName();
			}
		}

		public PlayerSettingsPanel(int playerNumber, PlayerSettings settings,
				List<Type> choices, boolean editable) {
			setLayout(new GridBagLayout());

			this.playerNumber = playerNumber;

			if (editable) {
				createControls(settings);
			} else {
				createLabels();
			}

			setChoices(choices);

			setSettings(settings);
		}

		private String getTypeLabel(Type type) {
			switch (type) {
			case COMPUTER:
				return "Computer";
			case HUMAN:
				return "Mensch";
			case NETWORK:
			case VACANT:
				return "Netzwerk";
			}

			return null;
		}

		public void setChoices(List<Type> playerTypeChoices) {
			this.playerTypeChoices = playerTypeChoices;

			Vector<String> choices = new Vector<String>();
			for (Type t : playerTypeChoices) {
				choices.add(getTypeLabel(t));
			}

			if (playerType != null) {
				playerType.setModel(new DefaultComboBoxModel(choices));
			}
		}

		private void createControls(PlayerSettings settings) {
			GridBagConstraints c = new GridBagConstraints();
			GridBagConstraints c2 = new GridBagConstraints();
			colorButton = new JButton();
			nameField = new JTextField(settings.getName());

			colorButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					new ColorMenu();
				}
			});
			c.gridy = 0;
			add(colorButton, c);

			nameField.getDocument().addDocumentListener(new NameChangeListener());
			c2.gridy = 0;
			c2.weightx = 1;
			c2.fill = GridBagConstraints.HORIZONTAL;
			add(nameField, c2);

			playerType = new JComboBox();
			playerType.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					changeType();
				}
			});
			playerType.setPreferredSize(new Dimension(120, playerType
					.getPreferredSize().height));
			playerType.setMinimumSize(new Dimension(120,
					playerType.getMinimumSize().height));
			add(playerType, c);

			removeButton = new JButton("\u00d7");
			removeButton.setFont(removeButton.getFont().deriveFont(Font.BOLD));
			removeButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					remove();
				}
			});

			add(removeButton, c);
		}

		private void createLabels() {
			GridBagConstraints c = new GridBagConstraints();
			GridBagConstraints c2 = new GridBagConstraints();
			colorButton = new JButton();
			nameLabel = new JLabel();
			nameLabel.setBorder(new EmptyBorder(3, 7, 3, 7));
			nameLabel.putClientProperty("html.disable", Boolean.TRUE);

			colorButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					if (colorChangable) {
						new ColorMenu();
					}
				}
			});
			c.gridy = 0;
			add(colorButton, c);

			c2.gridy = 0;
			c2.weightx = 1;
			c2.fill = GridBagConstraints.HORIZONTAL;
			add(nameLabel, c2);

			typeLabel = new JLabel(" ");
			typeLabel.setBorder(new EmptyBorder(3, 7, 3, 7));
			typeLabel.setPreferredSize(new Dimension(120, typeLabel
					.getPreferredSize().height));
			typeLabel.setMinimumSize(new Dimension(120,
					typeLabel.getMinimumSize().height));
			add(typeLabel, c);
		}

		@Override
		public Dimension getPreferredSize() {
			Dimension preferredSize = super.getPreferredSize();
			for (Component component : this.getComponents()) {
				preferredSize
						.setSize(preferredSize.width, Math.max(preferredSize.height,
								component.getPreferredSize().height));
			}
			return preferredSize;
		}

		@Override
		public Dimension getMaximumSize() {
			Dimension preferredSize = super.getPreferredSize();
			for (Component component : this.getComponents()) {
				preferredSize
						.setSize(Integer.MAX_VALUE, Math.max(preferredSize.height,
								component.getPreferredSize().height));
			}
			return preferredSize;
		}

		private void changeName() {
			changePlayerNameEvent.emit(playerNumber, nameField.getText());
		}

		private void changeColor(Color c) {
			changePlayerColorEvent.emit(playerNumber, c);
		}

		private void changeType() {
			changePlayerTypeEvent.emit(playerNumber,
					playerTypeChoices.get(playerType.getSelectedIndex()));
		}

		private void remove() {
			removePlayerEvent.emit(playerNumber);
		}

		private class ColorMenu extends JPanel {
			ColorMenu() {
				setLayout(new GridLayout(4, 4, 1, 1));

				JPopupMenu menu = new JPopupMenu();

				for (Color c : PLAYER_COLORS) {
					if (playerColors.contains(c)) {
						add(new ColorButton(c, menu));
					} else {
						add(new JLabel(ImageUtil.createColorIcon(
								UIManager.getColor("PopupMenu.background"), 16, 0)));
					}
				}

				menu.setLayout(new BorderLayout());
				menu.add(this);
				menu.show(colorButton, 0, colorButton.getHeight());
			}

			private class ColorButton extends JLabel {
				ColorButton(final Color c, final JPopupMenu menu) {
					final Icon normalIcon = ImageUtil.createColorIcon(c, 16, 1);
					final Icon hoverIcon = ImageUtil.createColorIcon(c, 16, 2);

					setIcon(normalIcon);

					addMouseListener(new MouseAdapter() {
						@Override
						public void mouseEntered(MouseEvent e) {
							setIcon(hoverIcon);
						}

						@Override
						public void mouseExited(MouseEvent e) {
							setIcon(normalIcon);
						}

						@Override
						public void mouseClicked(MouseEvent e) {
							menu.setVisible(false);
							changeColor(c);
						}
					});
				}
			}
		}
	}

}
