package jrummikub.control;

import jrummikub.control.network.ConnectionControl;
import jrummikub.control.network.NetworkControl;
import jrummikub.model.GameSettings;
import jrummikub.model.GameState;
import jrummikub.model.IRoundState;
import jrummikub.server.DedicatedServer;
import jrummikub.server.DedicatedServer.ServerStatus;
import jrummikub.util.Connection;
import jrummikub.util.IListener;
import jrummikub.util.IListener1;
import jrummikub.util.IListener3;
import jrummikub.util.LoginData;
import jrummikub.view.IQuitWarningPanel.QuitMode;
import jrummikub.view.IView;
import jrummikub.view.IView.BottomPanelType;

/**
 * The application control controls the settings for a new games and create the
 * game control
 */
public class ApplicationControl {
	private SettingsControl settingsControl;
	private LoginControl loginControl;
	private NetworkControl networkControl;
	private SaveControl saveControl;
	private GameControl gameControl;
	private Connection tempConnection;
	private DedicatedServer server;

	private IView view;

	/**
	 * Creates a new application control
	 * 
	 * @param view
	 *            the view to use
	 */
	public ApplicationControl(final IView view) {
		this.view = view;
		saveControl = new SaveControl(view);

		addQuitGameHandlers();
		addSaveLoadHandlers();
		addNewNetworkGameHandler();

		saveControl.getLoadEvent().add(
				new IListener3<GameSettings, GameState, IRoundState>() {
					@Override
					public void handle(GameSettings settings,
							GameState gameState, IRoundState roundState) {
						abortControls();
						gameControl = new GameControl(settings, saveControl,
								view);
						addGameControlListeners(gameControl);
						gameControl.continueGame(gameState, roundState);
					}
				});
		saveControl.getLoadErrorEvent().add(new IListener() {
			@Override
			public void handle() {
				view.showLoadingError();
			}
		});
	}

	private void addSaveLoadHandlers() {
		view.getLoadEvent().add(new IListener() {
			@Override
			public void handle() {
				if (!isGameRunning()) {
					abortControls();
					startApplication();
					view.load();
					return;
				}
				view.getQuitWarningPanel().setMode(QuitMode.QUIT_GAME);
				view.showQuitWarningPanel(true);

				tempConnection = view.getQuitWarningPanel().getQuitEvent()
						.add(new IListener() {
							@Override
							public void handle() {
								abortControls();
								startApplication();
								view.showQuitWarningPanel(false);
								view.load();
								tempConnection.remove();
							}
						});
			}
		});
		
	}

	private void addNewNetworkGameHandler() {
		view.getNetworkGameEvent().add(new IListener() {
			@Override
			public void handle() {
				if (!isGameRunning()) {
					abortControls();
					createLoginControl(true);
				} else {
					view.getQuitWarningPanel().setMode(QuitMode.QUIT_GAME);
					view.showQuitWarningPanel(true);
					tempConnection = view.getQuitWarningPanel().getQuitEvent()
							.add(new IListener() {
								@Override
								public void handle() {
									view.showQuitWarningPanel(false);
									abortControls();
									createLoginControl(true);
									tempConnection.remove();
									tempConnection = null;
								}
							});
				}
			}
		});
	}

	private void addQuitGameHandlers() {
		view.getMenuNewGameEvent().add(new IListener() {
			@Override
			public void handle() {
				showRestartWarning();
			}
		});
		view.getQuitEvent().add(new IListener() {
			@Override
			public void handle() {
				if (!isGameRunning()) {
					System.exit(0);
				} else {
					showQuitWarning();
				}
			}
		});
		view.getQuitWarningPanel().getCancelEvent().add(new IListener() {
			@Override
			public void handle() {
				view.showQuitWarningPanel(false);
				if (tempConnection != null) {
					tempConnection.remove();
					tempConnection = null;
				}
			}
		});
	}

	private void showRestartWarning() {
		view.getQuitWarningPanel().setMode(QuitMode.QUIT_GAME);
		view.showQuitWarningPanel(true);
		tempConnection = view.getQuitWarningPanel().getQuitEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						abortControls();
						startApplication();
						view.showQuitWarningPanel(false);
						tempConnection.remove();
						tempConnection = null;
					}
				});
	}

	private void showQuitWarning() {
		view.getQuitWarningPanel().setMode(QuitMode.QUIT_PROCESS);
		view.showQuitWarningPanel(true);
		tempConnection = view.getQuitWarningPanel().getQuitEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						System.exit(0);
					}
				});
	}

	/**
	 * Checks if a game is currently running
	 * @return true when a game is running
	 */
	public boolean isGameRunning() {
		return gameControl != null || networkControl != null
				&& networkControl.isGameRunning();
	}

	/**
	 * Create a new network login control
	 */
	private void createLoginControl(boolean reset) {
		loginControl = new LoginControl(view, this);
		loginControl.getLoginEvent().add(new IListener1<LoginData>() {
			@Override
			public void handle(LoginData loginData) {
				createNetworkControl(loginData);
			}
		});
		loginControl.getCancelEvent().add(new IListener() {
			@Override
			public void handle() {
				startApplication();
			}
		});
		loginControl.startLogin(reset);
	}

	/**
	 * End all controls in case of e.g. quit event
	 */
	private void abortControls() {
		if (settingsControl != null) {
			settingsControl.abort();
			settingsControl = null;
		}

		if (gameControl != null) {
			gameControl.abortGame();
			gameControl = null;
		}

		if (loginControl != null) {
			loginControl.abort();
			loginControl = null;
		}

		if (networkControl != null) {
			networkControl.abort();
			networkControl = null;
		}

		view.showSidePanel(false);
		view.showScorePanel(false);
		view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
	}

	/**
	 * Starts the application by showing the game settings dialog panel
	 */
	public void startApplication() {
		view.showSidePanel(false);
		view.showScorePanel(false);
		view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
		saveControl.setGameSettings(null);
		saveControl.setGameState(null);

		settingsControl = new SettingsControl(view, new GameSettings());
		view.enableSave(false);

		settingsControl.getStartGameEvent().add(new IListener1<GameSettings>() {
			@Override
			public void handle(GameSettings settings) {
				view.enableSave(true);
				settingsControl = null;

				saveControl.setGameSettings(settings);

				gameControl = new GameControl(settings, saveControl, view);
				addGameControlListeners(gameControl);

				gameControl.startGame();
			}
		});
		settingsControl.startSettings();
	}

	/**
	 * Adds events listeners to game control events
	 * 
	 * @param gameControl
	 *            of current game
	 */
	private void addGameControlListeners(GameControl gameControl) {
		gameControl.getEndOfGameEvent().add(new IListener() {
			@Override
			public void handle() {
				startApplication();
			}
		});
	}

	/**
	 * Create a new network game control
	 * 
	 * @param loginData
	 *            users login data for channel
	 */
	private void createNetworkControl(LoginData loginData) {
		ConnectionControl connectionControl = new ConnectionControl(loginData);
		networkControl = new NetworkControl(loginData, connectionControl,
				saveControl, view);

		networkControl.getStopNetworkEvent().add(new IListener() {
			@Override
			public void handle() {
				startApplication();
			}
		});

		networkControl.getBackToLoginEvent().add(new IListener() {
			@Override
			public void handle() {
				networkControl = null;
				createLoginControl(false);
			}
		});

		networkControl.startNetwork();
	}

	/**
	 * Ensure the dedicated server is running
	 * 
	 * @param password
	 *            password to use, if empty "jrummikub" is used
	 * @return whether the server could be started
	 */
	public boolean startDedicatedServer(String password) {
		if (server == null) {
			DedicatedServer newServer = new DedicatedServer(password);
			ServerStatus status = newServer.start();
			switch (status) {
			case STARTED:
				server = newServer;
				break;
			case ALREADY_RUNNING:
				view.showServerStartupError(true);
				return false;
			case ERROR:
				view.showServerStartupError(false);
				return false;
			}
		}
		server.setServerPassword(password);
		view.getLoginPanel().setServer(server.getHostName());
		view.getLoginPanel().setChannel(
				"jrummikub@play." + server.getHostName());
		view.getLoginPanel().setDedicatedServerRunning(true);
		return true;
	}

	/**
	 * If the login given is to our own dedicated server, update it's password
	 * to match
	 * 
	 * @param loginData
	 *            login data of user trying to connect
	 */
	public void updateDedicatedServerPassword(LoginData loginData) {
		if (server != null && server.getHostName() == loginData.getServerName()) {
			server.setServerPassword(loginData.getPassword());
		}
	}
}
