package jrummikub.control;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jrummikub.model.IGameState;
import jrummikub.model.IHand;
import jrummikub.model.ITable;
import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneSet;
import jrummikub.util.Connection;
import jrummikub.util.Event;
import jrummikub.util.IEvent;
import jrummikub.util.IListener;
import jrummikub.util.Pair;
import jrummikub.view.IView;

/**
 * Controller that manages a single round of rummikub
 */
public class RoundControl {
	private IGameState gameState;
	private IView view;
	private ITable clonedTable;
	private Event endRoundEvent = new Event();
	private List<Connection> connections = new ArrayList<Connection>();

	/**
	 * Create a new RoundControl using the given gameState and view
	 * 
	 * @param gameState
	 *            initial game state
	 * @param view
	 *            view used for user interaction
	 */
	public RoundControl(IGameState gameState, IView view) {
		this.gameState = gameState;
		this.view = view;
	}

	/**
	 * End the round
	 * 
	 * @return endRoundEvent
	 */
	public IEvent getEndRoundEvent() {
		return endRoundEvent;
	}

	/**
	 * Begin the round
	 */
	public void startRound() {
		deal();

		connections.add(view.getStartTurnEvent().add(new IListener() {

			@Override
			public void handle() {
				startTurn();
			}
		}));

		prepareTurn();
	}

	private void prepareTurn() {
		clonedTable = (ITable) gameState.getTable().clone();
		view.enableStartTurnPanel(true);
		view.getTablePanel().setStoneSets(clonedTable);
		view.setCurrentPlayerName(gameState.getActivePlayer().getName());
		view.getTablePanel().setLeftPlayerName(
				gameState.getNthNextPlayer(1).getName());
		view.getTablePanel().setTopPlayerName(
				gameState.getNthNextPlayer(2).getName());
		view.getTablePanel().setRightPlayerName(
				gameState.getNthNextPlayer(3).getName());
	}

	private void startTurn() {
		TurnControl turnControl = new TurnControl(gameState.getActivePlayer()
				.getHand(), clonedTable, view);
		connections.add(turnControl.getEndOfTurnEvent().add(new IListener() {

			@Override
			public void handle() {
				endOfTurn();
			}
		}));

		turnControl.startTurn();
	}

	void deal() {
		for (int i = 0; i < gameState.getPlayerCount(); i++) {
			IHand hand = gameState.getNthNextPlayer(i).getHand();
			for (int j = 0; j < 7; j++) {
				hand.drop(gameState.getGameHeap().drawStone(), new Position(j,
						0));
				hand.drop(gameState.getGameHeap().drawStone(), new Position(j,
						1));
			}
		}
	}

	private void endOfTurn() {
		Set<Stone> tableDiff = tableDifference(gameState.getTable(),
				clonedTable);

		if (!tableDiff.isEmpty()) { // Player has made a move
			if (clonedTable.isValid()) {
				gameState.setTable(clonedTable);

				if (gameState.getActivePlayer().getHand().getSize() == 0) {
					win();
					return;
				}
			} else {
				gameState.getGameHeap().putBack(tableDiff);
				dealPenalty(tableDiff.size());
			}
		} else { // Player hasn't made a move
			if (clonedTable.isValid()) {
				gameState.setTable(clonedTable);
			}

			dealStone();
		}

		gameState.nextPlayer();
		prepareTurn();
	}

	static Set<Stone> tableDifference(ITable oldTable, ITable newTable) {
		Set<Stone> ret = new HashSet<Stone>();

		for (Pair<StoneSet, Position> entry : newTable) {
			for (Stone stone : entry.getFirst()) {
				ret.add(stone);
			}
		}
		for (Pair<StoneSet, Position> entry : oldTable) {
			for (Stone stone : entry.getFirst()) {
				ret.remove(stone);
			}
		}

		return ret;
	}

	void dealStone() {
		gameState
				.getActivePlayer()
				.getHand()
				.drop(gameState.getGameHeap().drawStone(),
						new Position(GameControl.HAND_WIDTH - 1,
								GameControl.HAND_HEIGHT - 1));
	}

	private void dealPenalty(int count) {
		for (int i = 0; i < count + 3; ++i)
			dealStone();
	}

	private void win() {
		for (Connection c : connections) {
			c.remove();
		}
		endRoundEvent.emit();
		view.enableWinPanel(true);
	}
}
