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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Box;
import javax.swing.BoxLayout;
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.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.control.turn.TurnControlFactory;
import jrummikub.control.turn.TurnControlFactory.Type;
import jrummikub.model.GameSettings;
import jrummikub.model.PlayerSettings;
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 JTabbedPane tabbedPane;
	private JPanel playerSetupPanel;
	private JPanel playerSettingsViewport;
	private JPanel optionsPanel;
	private JButton addPlayerButton;
	private JLabel errorMessageLabel;
	private JButton startButton;
	private JCheckBox noLimitsBox;

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

	private JSpinner initialMeldThresholdSpinner;
	private JSpinner stoneSetNumberSpinner;
	private JSpinner highestValueSpinner;
	private JSpinner numberOfStonesDealtSpinner;
	private JSpinner jokerNumberSpinner;
	private JSpinner timeSpinner;

	private JPanel colorSelectionPanel;
	private Map<StoneColor, JToggleButton> colorButtons = new HashMap<StoneColor, JToggleButton>();

	private Event startGameEvent = new Event();
	private Event loadGameEvent = new Event();
	private Event networkGameEvent = 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, TurnControlFactory.Type> changePlayerTypeEvent = new Event2<Integer, TurnControlFactory.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>();

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

	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 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);
	}

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

	@Override
	public void enableRemovePlayerButtons(boolean enable) {
		removeButtonsEnabled = enable;
		for (PlayerSettingsPanel psp : playerSettingsPanels) {
			psp.enableRemoveButton(enable);
		}
	}

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

		for (int i = 0; i < gameSettings.getPlayerList().size(); ++i) {
			updatePlayerSettingsPanel(i, gameSettings.getPlayerList().get(i));
		}

		initialMeldThresholdSpinner.setValue(gameSettings
				.getInitialMeldThreshold());
		stoneSetNumberSpinner.setValue(gameSettings.getStoneSetNumber());
		highestValueSpinner.setValue(gameSettings.getHighestValue());
		numberOfStonesDealtSpinner.setValue(gameSettings
				.getNumberOfStonesDealt());
		jokerNumberSpinner.setValue(gameSettings.getJokerNumber());
		timeSpinner.setValue(gameSettings.getTime());
		noLimitsBox.setSelected(gameSettings.isNoLimits());

		for (StoneColor color : StoneColor.values()) {
			colorButtons.get(color).getModel()
					.setSelected(gameSettings.getStoneColors().contains(color));
		}

		playerSettingsViewport.revalidate();
	}

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

	private void addPlayerSettingsPanel(int i, PlayerSettings settings) {
		PlayerSettingsPanel panel = new PlayerSettingsPanel(i, settings);
		playerSettingsPanels.add(panel);
		playerSettingsViewport.add(panel,
				playerSettingsViewport.getComponentCount() - 1);
	}

	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);

		makeOptionLabel(0, "Auslegeschranke:");
		initialMeldThresholdSpinner = makeOptionSpinner(0, 1, 999, 1,
				changeInitialMeldThresholdEvent);

		makeOptionLabel(1, "Anzahl Steins\u00e4tze:");
		stoneSetNumberSpinner = makeOptionSpinner(1, 1, 999, 1,
				changeStoneSetNumberEvent);

		makeOptionLabel(2, "H\u00f6chster Steinwert:");
		highestValueSpinner = makeOptionSpinner(2, 3, 99, 1,
				changeHighestValueEvent);

		makeOptionLabel(3, "Anzahl Startsteine:");
		numberOfStonesDealtSpinner = makeOptionSpinner(3, 1, 999, 1,
				changeNumberOfStonesDealtEvent);

		makeOptionLabel(4, "Jokeranzahl:");
		jokerNumberSpinner = makeOptionSpinner(4, 1, 999, 1,
				changeJokerNumberEvent);

		makeOptionLabel(5, "Zeit für Spielzug:");
		timeSpinner = makeOptionSpinner(5, 1, 999, 1, changeTimeEvent);

		makeOptionLabel(6, "No Limits:");
		noLimitsBox = makeOptionCheckbox(6, changeNoLimitsEvent);

		makeOptionLabel(7, "Steinfarben:");
		createColorSelectionPanel(7);

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

	private void createColorSelectionPanel(int row) {
		GridBagConstraints c = new GridBagConstraints();
		c.anchor = GridBagConstraints.WEST;
		c.gridx = 1;
		c.gridy = row;

		colorSelectionPanel = new JPanel();
		optionsPanel.add(colorSelectionPanel, c);

		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 JCheckBox makeOptionCheckbox(int row,
			final Event1<Boolean> targetEvent) {
		GridBagConstraints c = new GridBagConstraints();
		c.anchor = GridBagConstraints.WEST;
		c.gridx = 1;
		c.gridy = row;
		c.insets = new Insets(2, 2, 2, 2);
		final JCheckBox box = new JCheckBox();
		box.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				targetEvent.emit(box.isSelected());
			}
		});
		optionsPanel.add(box, c);
		return box;
	}

	private JSpinner makeOptionSpinner(int row, int min, int max, int step,
			final Event1<Integer> targetEvent) {
		GridBagConstraints c = new GridBagConstraints();
		c.anchor = GridBagConstraints.WEST;
		c.gridx = 1;
		c.gridy = row;
		c.insets = new Insets(2, 2, 2, 2);
		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());
			}
		});
		optionsPanel.add(spinner, c);
		return spinner;
	}

	private void 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);
	}

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

		tabbedPane = new JTabbedPane();

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

		createOptionsPanel();
		tabbedPane.addTab("Optionen", optionsPanel);

		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);

		c.gridwidth = 1;
		startButton = addButton("Spiel starten", startGameEvent, c);

		c.weightx = 0;
		add(Box.createHorizontalStrut(10), c);

		c.weightx = 1;
		addButton("Spiel laden...", loadGameEvent, c);

		c.weightx = 0;
		add(Box.createHorizontalStrut(10), c);

		c.weightx = 1;
		c.gridwidth = GridBagConstraints.REMAINDER;
		addButton("Netzwerkspiel...", networkGameEvent, c);

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

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

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

	private class PlayerSettingsPanel extends JPanel {
		private int playerNumber;
		private JButton colorButton;
		private JTextField nameField;
		private JButton removeButton;
		private JComboBox playerType;

		public void setSettings(PlayerSettings settings) {
			if (!nameField.getText().equals(settings.getName())) {
				nameField.setText(settings.getName());
			}
			// playerType.setSelectedIndex(0);
			int index = Arrays.binarySearch(TurnControlFactory.Type.values(),
					settings.getTurnControlType());
			if (index != playerType.getSelectedIndex()) {
				playerType.setSelectedIndex(index);
			}
			colorButton.setIcon(ImageUtil.createColorIcon(settings.getColor(),
					16, 2));
		}

		public void enableRemoveButton(boolean enable) {
			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) {
			setLayout(new GridBagLayout());

			this.playerNumber = playerNumber;
			createControls(settings);

			setSettings(settings);
		}

		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);

			String[] choices = { "Mensch", "Computer" };

			playerType = new JComboBox(choices);
			playerType.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					changeType();
				}
			});
			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();
				}
			});
			removeButton.setEnabled(removeButtonsEnabled);
			add(removeButton, 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, TurnControlFactory.Type
					.values()[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) {
					add(new ColorButton(c, menu));
				}

				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);
						}
					});
				}
			}
		}
	}

}