package jrummikub.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jrummikub.model.GameSettings;
import jrummikub.model.GameState;
import jrummikub.model.IPlayer;
import jrummikub.model.IRoundState;
import jrummikub.model.PlayerSettings;
import jrummikub.model.RoundState;
import jrummikub.model.Score;
import jrummikub.util.Connection;
import jrummikub.util.Event;
import jrummikub.util.IEvent;
import jrummikub.util.IListener;
import jrummikub.util.IListener1;
import jrummikub.view.IView;
import jrummikub.view.IView.BottomPanelType;

/**
 * Controls a Game, at some point including all Rounds, starts new Rounds
 */
public class GameControl {
	protected SaveControl saveControl;
	protected GameSettings gameSettings;
	protected IView view;
	protected RoundControl roundControl;
	protected GameState gameState;
	protected List<Connection> connections = new ArrayList<Connection>();

	private Event endOfGameEvent = new Event();

	/**
	 * Constructor
	 * 
	 * @param gameSettings
	 *          the game settings
	 * @param saveControl
	 *          the save control
	 * @param view
	 *          the view
	 */
	public GameControl(GameSettings gameSettings, SaveControl saveControl,
			IView view) {
		this.gameSettings = gameSettings;
		this.saveControl = saveControl;
		this.view = view;

		if (gameSettings != null) {
			gameState = new GameState();
			saveControl.setGameState(gameState);

			gameState.setFirstRoundFirstPlayer((int) (Math.random() * gameSettings
					.getPlayerList().size()));
		}

		connections.add(view.getNewRoundEvent().add(new IListener() {
			@Override
			public void handle() {
				startRound();
			}
		}));

		connections.add(view.getNewGameEvent().add(new IListener() {
			@Override
			public void handle() {
				endGame();
			}
		}));

		connections.add(view.getEndProgramEvent().add(new IListener() {
			@Override
			public void handle() {
				endProgram();
			}
		}));
	}

	/**
	 * Is emitted when the user ends the game and start a new one
	 * 
	 * @return the endOfGameEvent
	 */
	public IEvent getEndOfGameEvent() {
		return endOfGameEvent;
	}

	/**
	 * Ends the running game
	 */
	private void endGame() {
		removeListeners();
		endOfGameEvent.emit();
	}

	/**
	 * Removes all listeners from the connection
	 */
	private void removeListeners() {
		for (Connection c : connections) {
			c.remove();
		}
	}

	/**
	 * Game gets started by initializing the first Round
	 */
	public void startGame() {
		startRound();
	}

	/**
	 * Continues game after loading
	 * 
	 * @param gameState
	 *          the saved GameState (Players, startplayer, points)
	 * @param roundState
	 *          the saved RoundState (activePlayer, Table, heap etc)
	 */
	public void continueGame(GameState gameState, IRoundState roundState) {
		this.gameState = gameState;
		if (roundState == null) {
			showScorePanel();
		} else {
			prepareRound(roundState);
			roundControl.continueRound();
		}
	}

	/**
	 * Clean abortion of a running game when another one is loaded or when a new
	 * one is started
	 * 
	 */
	public void abortGame() {
		removeListeners();
		if (roundControl != null) {
			roundControl.abortRound();
			roundControl = null;
		}

		view.clearView();
	}

	/**
	 * Start a new round within the existing game
	 */
	protected void startRound() {
		if (roundControl != null) {
			return;
		}

		view.showScorePanel(false);

		IRoundState roundState = createRoundState();
		prepareRound(roundState);
		roundControl.startRound();
	}

	/**
	 * Prepare a new round by setting start player, adding listeners
	 * 
	 * @param roundState
	 *          of current round
	 */
	private void prepareRound(IRoundState roundState) {
		saveControl.setRoundState(roundState);

		if (roundState != null) {
			roundState.setActivePlayerNumber(gameState.getFirstRoundFirstPlayer()
					+ gameState.getScores().size());
		}

		roundControl = createRoundControl(roundState);
		roundControl.getRoundStateUpdateEvent().add(new IListener1<IRoundState>() {
			@Override
			public void handle(IRoundState newState) {
				gameState = newState.getGameState();
				gameSettings = newState.getGameSettings();
			}
		});
		roundControl.getEndOfRoundEvent().add(new IListener1<Score>() {
			@Override
			public void handle(Score roundScore) {
				gameState.setLastPlayerRedealed(null);
				endOfRound(roundScore);
			}
		});

		roundControl.getRestartRoundEvent().add(new IListener1<PlayerSettings>() {
			@Override
			public void handle(PlayerSettings player) {
				gameState.setLastPlayerRedealed(player);
				restartRound();
			}
		});
	}

	/**
	 * Creates a new round State
	 * 
	 * @return the round state
	 */
	protected IRoundState createRoundState() {
		return new RoundState(gameSettings, gameState);
	}

	/**
	 * Creates a new round control with the specified round state
	 * 
	 * @param roundState
	 *          for new round control
	 * @return the round control
	 */
	protected RoundControl createRoundControl(IRoundState roundState) {
		return new RoundControl(roundState, view);
	}

	/**
	 * Restarts round after redealing
	 */
	private void restartRound() {
		roundControl = null;
		startRound();
	}

	/**
	 * Sets the score and default values for saving when round ends
	 * 
	 * @param roundScore
	 *          score for ended round
	 */
	private void endOfRound(Score roundScore) {
		gameState.getScores().add(roundScore);
		saveControl.setRoundState(null);

		roundControl = null;
		showScorePanel();
	}

	/**
	 * Sets score panel visible
	 */
	private void showScorePanel() {
		view.getSidePanel().setPlayers(Collections.<IPlayer> emptyList());
		showWinPanel();

		view.getScorePanel().setPlayers(gameSettings.getPlayerList());
		view.getScorePanel().setScores(gameState.getScores());
		view.getScorePanel().setAccumulatedScore(gameState.getAccumulatedScore());
		view.getScorePanel().update();
		view.showScorePanel(true);
	}

	protected void showWinPanel() {
		view.setBottomPanel(BottomPanelType.WIN_PANEL);
	}

	/**
	 * Exits System without warnings if no game control is active
	 */
	private void endProgram() {
		System.exit(0);
	}

}
