package jrummikub.control;

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

import jrummikub.model.Hand;
import jrummikub.model.IRoundState;
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 IRoundState roundState;
	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(IRoundState roundState, IView view) {
		this.roundState = roundState;
		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) roundState.getTable().clone();
		view.enableStartTurnPanel(true);
		view.getTablePanel().setStoneSets(clonedTable);
		view.setCurrentPlayerName(roundState.getActivePlayer()
				.getPlayerSettings().getName());
		view.getTablePanel().setLeftPlayerName(
				roundState.getNthNextPlayer(1).getPlayerSettings().getName());
		view.getTablePanel().setTopPlayerName(
				roundState.getNthNextPlayer(2).getPlayerSettings().getName());
		view.getTablePanel().setRightPlayerName(
				roundState.getNthNextPlayer(3).getPlayerSettings().getName());
	}

	private void startTurn() {
		TurnControl turnControl = new TurnControl(roundState.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 < roundState.getPlayerCount(); i++) {
			IHand hand = roundState.getNthNextPlayer(i).getHand();
			for (int j = 0; j < 7; j++) {
				hand.drop(roundState.getGameHeap().drawStone(), new Position(j,
						0));
				hand.drop(roundState.getGameHeap().drawStone(), new Position(j,
						1));
			}
		}
	}

	/**
	 * after a legal move
	 * 
	 * @return win or no win
	 */
	private boolean postLegalMove() {
		roundState.setTable(clonedTable);

		if (roundState.getActivePlayer().getHand().getSize() == 0) {
			win();
			return true;
		}
		return false;
	}

	private boolean notLaidOutYet(Set<Stone> tableDiff) {
		boolean win = false;
		if (tableSetDifference(clonedTable, roundState.getTable()).isEmpty()) {
			// laid sthg out and didn't change table
			List<StoneSet> newSets = tableSetDifference(roundState.getTable(),
					clonedTable);

			int totalValue = 0;
			for (StoneSet set : newSets) {
				totalValue += set.classify().getSecond();
			}

			if (totalValue >= roundState.getGameSettings()
					.getInitialMeldThreshold()) {

				roundState.getActivePlayer().setLaidOut(true);
				win = postLegalMove();
				return win;
			} else {
				// deal penalty, reset
				roundState.getGameHeap().putBack(tableDiff);
				dealPenalty(tableDiff.size());
				return win;
			}
		} else {
			// deal penalty, reset
			roundState.getGameHeap().putBack(tableDiff);
			dealPenalty(tableDiff.size());
			return win;
		}
	}

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

		if (tableDiff.isEmpty()) {
			// Player hasn't made a move
			if (clonedTable.isValid()) {
				roundState.setTable(clonedTable);
			}
			dealStone();
		} else {
			// Player has made a move
			if (!clonedTable.isValid()) {
				// deal penalty, reset
				roundState.getGameHeap().putBack(tableDiff);
				dealPenalty(tableDiff.size());
			} else {
				if (roundState.getActivePlayer().getLaidOut()) {
					// Player has laid out
					if (postLegalMove()) {
						return;
					}
				} else {
					// Player hasn't laid out
					if (notLaidOutYet(tableDiff)) {
						return;
					}
				}
			}
		}
		roundState.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;
	}

	static List<StoneSet> tableSetDifference(ITable oldTable, ITable newTable) {
		List<StoneSet> ret = new ArrayList<StoneSet>();

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

	void dealStones(int count) {
		IHand hand = roundState.getActivePlayer().getHand();
		int rowCount = hand.getRowCount();

		for (int i = 0; i < count; ++i) {
			if (hand.getFreeRowSpace(rowCount - 1) == 0) {
				rowCount++;
			}

			hand.drop(roundState.getGameHeap().drawStone(), new Position(
					Hand.WIDTH - 1, rowCount - 1));
		}
	}

	private void dealStone() {
		dealStones(1);
	}

	private void dealPenalty(int count) {
		dealStones(count + 3);
	}

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