package jrummikub.control.network;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import jrummikub.control.SaveControl;
import jrummikub.model.GameSettings;
import jrummikub.util.Connection;
import jrummikub.util.Event;
import jrummikub.util.GameData;
import jrummikub.util.IEvent;
import jrummikub.util.IListener;
import jrummikub.util.IListener1;
import jrummikub.util.LoginData;
import jrummikub.view.IView;
import jrummikub.view.IView.BottomPanelType;
import jrummikub.view.LoginError;

/**
 * Class dealing with network connection, offering and choice of network games
 */
public class NetworkControl {
	private IConnectionControl connectionControl;
	private IView view;
	private List<Connection> connections = new ArrayList<Connection>();
	private Event stopNetworkEvent = new Event();
	private Event backToLoginEvent = new Event();

	private NetworkSettingsControl settingsControl;
	private GameOfferControl gameOfferControl;
	private GameJoinControl gameJoinControl;

	private SaveControl saveControl;

	private NetworkGameControl gameControl;

	private List<UUID> games = new ArrayList<UUID>();
	private Map<UUID, GameData> gameMap = new HashMap<UUID, GameData>();

	/**
	 * Creates a new network control
	 * 
	 * @param loginData
	 *          user's login data
	 * @param connectionControl
	 *          current connection for events and messages
	 * @param saveControl
	 *          save control if saving will ever be allowed
	 * @param view
	 *          for events and handlers
	 */
	public NetworkControl(final LoginData loginData,
			IConnectionControl connectionControl, SaveControl saveControl,
			final IView view) {
		this.view = view;
		this.connectionControl = connectionControl;
		this.saveControl = saveControl;

		addConnectionSetupListeners(loginData);
		addConnectionControlListeners();
		addViewEventListeners();
	}

	private void addViewEventListeners() {
		connections.add(view.getGameListPanel().getJoinEvent()
				.add(new IListener1<GameData>() {
					@Override
					public void handle(GameData gameData) {
						join(gameData);
					}
				}));

		connections.add(view.getGameListPanel().getOpenNewGameEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						createSettingsControl();
					}
				}));

		connections.add(view.getGameListPanel().getCancelEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						abort();
						stopNetworkEvent.emit();
					}
				}));
	}

	private void join(GameData gameData) {
		view.showGameListPanel(false);
		connectionControl.joinGame(gameData);
	}

	/**
	 * Adds the listeners for connection control events
	 */
	private void addConnectionControlListeners() {
		addOfferUpdateListener();

		connections.add(connectionControl.getGameWithdrawalEvent().add(
				new IListener1<UUID>() {
					@Override
					public void handle(UUID uuid) {
						games.remove(uuid);
						gameMap.remove(uuid);

						updateGameList();
					}
				}));
		connections.add(connectionControl.getGameJoinAckEvent().add(
				new IListener1<Boolean>() {
					@Override
					public void handle(Boolean ack) {
						if (ack) {
							createGameJoinControl();
						} else {
							view.showGameListPanel(true);
						}
					}
				}));
		addConnectionLostListeners();
	}
	
	/**
	 * Adds the listeners for lost connection events
	 */
	private void addConnectionLostListeners() {
		connections.add(connectionControl.getParticipantLeftEvent().add(
				new IListener1<String>() {
					@Override
					public void handle(String nickname) {
						for (Map.Entry<UUID, GameData> entry : gameMap.entrySet()) {
							if (entry.getValue().getHost().equals(nickname)) {
								games.remove(entry.getKey());
								gameMap.remove(entry.getKey());
								break;
							}
						}

						updateGameList();
					}
				}));
		connections.add(connectionControl.getConnectionLostEvent().add(
				new IListener() {
					@Override
					public void handle() {
						abort();
						view.setBottomPanel(BottomPanelType.NETWORK_SERVER_CONNECTION_LOST_PANEL);
						connections.add(view.getAcknowledgeConnectionLostEvent().add(
								new IListener() {
									@Override
									public void handle() {
										abort();
										view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
										backToLoginEvent.emit();
									}
								}));
					}
				}));
	}

	private void addOfferUpdateListener() {
		connections.add(connectionControl.getGameOfferEvent().add(
				new IListener1<GameData>() {
					@Override
					public void handle(GameData gameData) {
						UUID uuid = gameData.getGameID();

						GameData game = gameMap.get(uuid);

						if (game == null) {
							game = gameData;
							gameMap.put(uuid, gameData);
							games.add(uuid);
						} else {
							game.setGameSettings(gameData.getGameSettings());
						}

						updateGameList();
					}
				}));
	}

	private void addConnectionSetupListeners(final LoginData loginData) {
		connections.add(connectionControl.getConnectedEvent().add(new IListener() {
			@Override
			public void handle() {
				view.getGameListPanel().setChannelName(loginData.getChannelName());
				view.showConnectPanel(false);
				view.showGameListPanel(true);
			}
		}));

		connections.add(connectionControl.getConnectionFailedEvent().add(
				new IListener1<LoginError>() {
					@Override
					public void handle(LoginError error) {
						view.getConnectPanel().setMode(error);
						view.showConnectPanel(true);
					}
				}));

		connections.add(view.getConnectPanel().getCancelEvent()
				.add(new IListener() {
					@Override
					public void handle() {
						abort();
						backToLoginEvent.emit();
					}
				}));
	}

	private void updateGameList() {
		List<GameData> gameList = new ArrayList<GameData>();

		for (UUID uuid : games) {
			gameList.add(gameMap.get(uuid));
		}

		view.getGameListPanel().setGameList(gameList);
	}

	/**
	 * Starts a new network connection with the specified data
	 */
	public void startNetwork() {
		view.showConnectPanel(true);
		view.getConnectPanel().setMode(null);
		connectionControl.connect();
	}

	/**
	 * Ends the network connection if canceled
	 */
	public void abort() {
		for (Connection c : connections) {
			c.remove();
		}

		abortControls();

		connectionControl.disconnect();
	}

	private void abortControls() {
		if (settingsControl != null) {
			settingsControl.abort();
			settingsControl = null;
		}
		if (gameOfferControl != null) {
			gameOfferControl.abort();
			gameOfferControl = null;
		}
		if (gameJoinControl != null) {
			gameJoinControl.abort();
			gameJoinControl = null;
		}
		if (gameControl != null) {
			gameControl.abortGame();
			gameControl = null;
		}

		view.showGameListPanel(false);
		view.showConnectPanel(false);
		view.showSidePanel(false);
		view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
	}

	/**
	 * Getter for stopNetworkEvent
	 * 
	 * @return stopNetworkEvent
	 */
	public IEvent getStopNetworkEvent() {
		return stopNetworkEvent;
	}

	/**
	 * The back to login event is emitted when the player aborted the connecting
	 * process or when a connection error has occured and been acknowledged
	 * 
	 * @return the event
	 */
	public IEvent getBackToLoginEvent() {
		return backToLoginEvent;
	}

	private void createSettingsControl() {
		if (settingsControl != null) {
			return;
		}
		view.showGameListPanel(false);

		settingsControl = new NetworkSettingsControl(
				connectionControl.getNickname(), view, new GameSettings());
		settingsControl.getOfferGameEvent().add(new IListener1<GameSettings>() {
			@Override
			public void handle(GameSettings settings) {
				settingsControl = null;
				createGameOfferControl(settings);
			}
		});
		settingsControl.getBackEvent().add(new IListener() {
			@Override
			public void handle() {
				settingsControl = null;
				view.showGameListPanel(true);
			}
		});
		settingsControl.startSettings();
	}

	private void createGameJoinControl() {
		if (gameJoinControl != null) {
			return;
		}

		GameData gameData = connectionControl.getCurrentGame();
		gameJoinControl = new GameJoinControl(connectionControl, gameData, view);
		gameJoinControl.getBackEvent().add(new IListener() {
			@Override
			public void handle() {
				gameJoinControl = null;
				view.showGameListPanel(true);
			}
		});
		gameJoinControl.getStartGameEvent().add(new IListener() {
			@Override
			public void handle() {
				createGameControl(null, false);
			}
		});
		gameJoinControl.startGameJoin();
	}

	private void createGameOfferControl(GameSettings settings) {
		if (gameOfferControl != null) {
			return;
		}
		gameOfferControl = new GameOfferControl(connectionControl, settings, view);
		gameOfferControl.getBackEvent().add(new IListener() {
			@Override
			public void handle() {
				gameOfferControl = null;
				view.showGameListPanel(true);
			}
		});
		gameOfferControl.getStartGameEvent().add(new IListener1<GameSettings>() {
			@Override
			public void handle(GameSettings settings) {
				createGameControl(settings, true);
			}
		});
		gameOfferControl.startGameOffer();
	}

	private void createGameControl(GameSettings settings, boolean host) {
		gameControl = new NetworkGameControl(settings, saveControl, view,
				connectionControl, host);
		gameControl.getBackEvent().add(new IListener() {
			@Override
			public void handle() {
				view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
				abortControls();
				view.showGameListPanel(true);
			}
		});
		gameControl.getEndOfGameEvent().add(new IListener() {
			@Override
			public void handle() {
				view.setBottomPanel(BottomPanelType.START_GAME_PANEL);
				abortControls();
				view.showGameListPanel(true);
			}
		});

		gameControl.startGame();
	}

	/**
	 * Returns if there is a running game
	 * 
	 * @return is there a game running
	 */
	public boolean isGameRunning() {
		return gameControl != null;
	}
}
