package jrummikub.control;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jrummikub.model.GameSettings;
import jrummikub.model.PlayerSettings;
import jrummikub.model.PlayerSettings.Type;
import jrummikub.model.StoneColor;
import jrummikub.util.Connection;
import jrummikub.util.IListener;
import jrummikub.util.IListener1;
import jrummikub.util.IListener2;
import jrummikub.view.ISettingsPanel;
import jrummikub.view.IView;

/**
 * Abstract control class managing joined methods and attributes of all
 * different (network, default...) settings controls
 */
public abstract class AbstractSettingsControl {
	protected IView view;
	protected List<Connection> connections = new ArrayList<Connection>();

	protected GameSettings settings;

	/**
	 * Create a new settings control
	 * 
	 * @param view
	 *            the view to use
	 * @param settings
	 *            initial game settings
	 */
	protected AbstractSettingsControl(IView view, GameSettings settings) {
		this.view = view;
		this.settings = settings;

		view.getSettingsPanel().setPlayerNamesEditable(
				Collections.<Boolean> emptyList());
		view.getSettingsPanel()
				.setPlayerColors(
						new HashSet<Color>(Arrays
								.asList(ISettingsPanel.PLAYER_COLORS)));
	}

	/**
	 * Add event listeners to abstract settings control
	 */
	protected void addListeners() {
		addPlayerSettingsListeners();

		addOptionListeners1();
		addOptionListeners2();
		addVariantListeners();
	}

	/**
	 * Add event listeners to the player settings panel
	 */
	private void addPlayerSettingsListeners() {
		connections.add(view.getSettingsPanel().getAddPlayerEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						addPlayer();
					}
				}));
		connections.add(view.getSettingsPanel().getRemovePlayerEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer i) {
						removePlayer(i);
					}
				}));
		connections.add(view.getSettingsPanel().getChangePlayerColorEvent()
				.add(new IListener2<Integer, Color>() {
					@Override
					public void handle(Integer i, Color color) {
						setPlayerColor(i, color);
					}
				}));
		connections.add(view.getSettingsPanel().getChangePlayerNameEvent()
				.add(new IListener2<Integer, String>() {
					@Override
					public void handle(Integer i, String name) {
						setPlayerName(i, name);
					}
				}));
		connections.add(view.getSettingsPanel().getChangePlayerTypeEvent()
				.add(new IListener2<Integer, Type>() {
					@Override
					public void handle(Integer i, Type type) {
						setPlayerType(i, type);
					}
				}));
	}

	/**
	 * Add the first half of event listeners to option panel
	 */
	private void addOptionListeners1() {
		connections.add(view.getSettingsPanel()
				.getChangeInitialMeldThresholdEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setInitialMeldThreshold(value);
						update();
					}
				}));

		connections.add(view.getSettingsPanel().getChangeJokerNumberEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setJokerNumber(value);
						update();
					}
				}));

		connections.add(view.getSettingsPanel().getChangeStoneSetNumberEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setStoneSetNumber(value);
						update();
					}
				}));
		connections.add(view.getSettingsPanel()
				.getChangeNumberOfStonesDealtEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setNumberOfStonesDealt(value);
						update();
					}
				}));
	}

	/**
	 * Add the second half of event listeners to option panel
	 */
	private void addOptionListeners2() {
		connections.add(view.getSettingsPanel().getChangeHighestValueEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setHighestValue(value);
						update();
					}
				}));
		connections.add(view.getSettingsPanel().getChangeTimeEvent()
				.add(new IListener1<Integer>() {
					@Override
					public void handle(Integer value) {
						settings.setTotalTime(value);
						update();
					}
				}));
		connections.add(view.getSettingsPanel().getChangeStoneColorsEvent()
				.add(new IListener1<Set<StoneColor>>() {
					@Override
					public void handle(Set<StoneColor> value) {
						settings.setStoneColors(new HashSet<StoneColor>(value));
						update();
					}
				}));

		connections.add(view.getSettingsPanel().getChangeNoLimitsEvent()
				.add(new IListener1<Boolean>() {
					@Override
					public void handle(Boolean value) {
						settings.setNoLimits(value);
						update();
					}
				}));

		connections.add(view.getSettingsPanel().getChangeSeeHandSizeEvent()
				.add(new IListener1<Boolean>() {
					@Override
					public void handle(Boolean value) {
						settings.setSeeHandSize(value);
						update();
					}
				}));
	}

	/**
	 * Add event listeners for variants, e.g. standard or children
	 */
	private void addVariantListeners() {
		connections.add(view.getSettingsPanel().getSetVariantDefaultEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						settings.reset();
						update();
					}
				}));
		connections.add(view.getSettingsPanel().getSetVariantChildrenEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						settings.reset();
						settings.setHighestValue(10);
						settings.setJokerNumber(6);
						settings.setTotalTime(120);
						settings.setInitialMeldThreshold(20);
						update();
					}
				}));
	}

	/**
	 * Start the operation of the settings control
	 */
	public void startSettings() {
		view.showSettingsPanel(true);
	}

	/**
	 * Finds the next unused player color
	 * 
	 * @return an unused color
	 */
	protected Color findUnusedColor() {
		Color color = null;
		colorLoop: for (Color c : ISettingsPanel.PLAYER_COLORS) {
			color = c;
			for (PlayerSettings player : settings.getPlayerList()) {
				if (c == player.getColor()) {
					continue colorLoop;
				}
			}
			break;
		}
		return color;
	}

	/**
	 * Sets a player's color
	 * 
	 * @param i
	 *            index of player
	 * @param color
	 *            color to be set
	 */
	private void setPlayerColor(int i, Color color) {
		PlayerSettings player = settings.getPlayerList().get(i);

		if (player.getColor() == color) {
			return;
		}

		for (PlayerSettings other : settings.getPlayerList()) {
			if (other.getColor() == color) {
				other.setColor(player.getColor());
				break;
			}
		}

		player.setColor(color);
		update();
	}

	/**
	 * Sets a player's name
	 * 
	 * @param i
	 *            index of player
	 * @param name
	 *            name to be set
	 */
	private void setPlayerName(int i, String name) {
		settings.getPlayerList().get(i).setName(name);
		update();
	}

	/**
	 * Removes a player from the planned game
	 * 
	 * @param i
	 *            index of player
	 */
	private void removePlayer(int i) {
		settings.getPlayerList().remove(i);
		update();
	}

	/**
	 * Checks the settings for ambiguous or missing data and sets error/warning
	 * if necessary
	 * 
	 * @return boolean check correct
	 */
	protected boolean checkSettings() {
		if (!checkName()) {
			return false;
		}
		if (!checkErrors()) {
			return false;
		}

		view.getSettingsPanel().setError(ISettingsPanel.SettingsError.NO_ERROR);
		view.getSettingsPanel().enableStartGameButton(true);

		checkWarnings();

		return true;
	}

	/**
	 * Checks if unique player name is present for each player
	 * 
	 * @return is player name ok
	 */
	private boolean checkName() {
		for (PlayerSettings player : settings.getPlayerList()) {
			if (player.getName().isEmpty()) {
				view.getSettingsPanel().setError(
						ISettingsPanel.SettingsError.NO_PLAYER_NAME_ERROR);
				view.getSettingsPanel().enableStartGameButton(false);
				return false;
			}
		}
		for (int i = 0; i < settings.getPlayerList().size(); ++i) {
			PlayerSettings player1 = settings.getPlayerList().get(i);
			String name = player1.getName();
			Type type = player1.getType();

			if (type == Type.VACANT) {
				continue;
			}

			for (int j = i + 1; j < settings.getPlayerList().size(); ++j) {
				PlayerSettings player2 = settings.getPlayerList().get(j);
				if (player2.getName().equals(name) && player2.getType() == type) {
					view.getSettingsPanel()
							.setError(
									ISettingsPanel.SettingsError.DUPLICATE_PLAYER_NAME_ERROR);
					view.getSettingsPanel().enableStartGameButton(false);
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Check options leading to errors
	 * 
	 * @return options ok
	 */
	private boolean checkErrors() {
		int totalStonesDealt = settings.getNumberOfStonesDealt()
				* settings.getPlayerList().size();
		int totalStones = settings.getTotalStones();

		if (totalStones <= totalStonesDealt) {
			view.getSettingsPanel().setError(
					ISettingsPanel.SettingsError.NOT_ENOUGH_STONES_ERROR);
			view.getSettingsPanel().enableStartGameButton(false);
			return false;
		}

		if (settings.getStoneColors().size() < 3) {
			view.getSettingsPanel().setError(
					ISettingsPanel.SettingsError.NOT_ENOUGH_COLORS_ERROR);
			view.getSettingsPanel().enableStartGameButton(false);
			return false;
		}

		return true;
	}

	/**
	 * Check all options and player settings for entries causing warnings
	 */
	private void checkWarnings() {
		if (settings.getInitialMeldThreshold() >= 100) {
			view.getSettingsPanel().setError(
					ISettingsPanel.SettingsError.TOO_HIGH_THRESHOLD_WARNING);
			return;
		}

		boolean humanPlayerFound = false;
		for (PlayerSettings player : settings.getPlayerList()) {
			if (player.getType() == Type.HUMAN) {
				humanPlayerFound = true;
				break;
			}
		}

		if (!humanPlayerFound) {
			view.getSettingsPanel().setError(
					ISettingsPanel.SettingsError.COMPUTER_PLAYERS_ONLY_WARNING);
			return;
		}
	}

	/**
	 * Abort settings control once settings are set
	 */
	public void abort() {
		view.showSettingsPanel(false);
		for (Connection c : connections) {
			c.remove();
		}
	}

	/**
	 * Add new player to the planned game
	 */
	abstract protected void addPlayer();

	/**
	 * Set the type of a player
	 * 
	 * @param i
	 *            index of player
	 * @param type
	 *            type to be set
	 */
	abstract protected void setPlayerType(int i, Type type);

	/**
	 * Show newly chosen options in view
	 */
	abstract protected void update();

}
