package jrummikub.control.turn;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingUtilities;
import javax.swing.Timer;

import jrummikub.ai.TurnLogic;
import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneSet;
import jrummikub.util.Pair;

/**
 * Base class for AI players
 * 
 */
public class AIControl extends AbstractTurnControl {
	private TurnLogic logic;
	/**
	 * Does the AI control currently use an internal timer
	 */
	public static boolean useBackgroundThread = true;
	long startTime;

	private boolean isPaused = false;
	private boolean turnDone = false;
	private boolean readyToEmit = false;
	private boolean aborted = false;

	@Override
	protected void doStartTurn() {
		timer.startTimer();
		startTime = System.currentTimeMillis();
		compute();
	}

	@Override
	protected void timeOut() {
		executeTurn();
	}

	@Override
	protected void pauseTurn() {
		super.pauseTurn();
		isPaused = true;
	}

	@Override
	protected void resumeTurn() {
		super.resumeTurn();
		isPaused = false;
		if (readyToEmit) {
			emitEndOfTurn();
		}
	}

	@Override
	protected void cleanUp() {
		if (logic != null)
			logic.abort();
		turnDone = true;
		super.cleanUp();
	}

	@Override
	public void abortTurn() {
		aborted = true;
		super.abortTurn();
	}

	private void compute() {
		switch (turnInfo.getTurnMode()) {
		case MAY_REDEAL:
			endTurn();
			break;
		case INSPECT_ONLY:
			endTurn();
			break;
		case NORMAL_TURN:
			turn();
			break;
		}
	}

	private void turn() {
		List<Stone> tableStones = new ArrayList<Stone>();
		List<Stone> handStones = new ArrayList<Stone>();

		addHandStones(handStones);

		addTableStones(tableStones);

		logic = new TurnLogic(settings, tableStones, handStones);

		if (!turnInfo.getLaidOut()) {
			logic.needIntialMeldThreshold();
		}
		if (useBackgroundThread) {

			Timer timer = new Timer(10000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					logic.autoAbort();
				}
			});
			timer.setRepeats(false);
			timer.start();

			Thread computeThread = new Thread(new Runnable() {
				@Override
				public void run() {
					logic.optimize();
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							executeTurn();
						}
					});
				}
			});
			computeThread.start();
		} else {
			logic.optimize();
			executeTurn();
		}

	}

	private void addHandStones(List<Stone> handStones) {
		for (Pair<Stone, Position> entry : turnInfo.getHand()) {
			handStones.add(entry.getFirst());
		}
	}

	private void addTableStones(List<Stone> tableStones) {
		if (turnInfo.getLaidOut()) {
			for (Pair<StoneSet, Position> entry : turnInfo.getTable()) {
				for (Stone stone : entry.getFirst()) {
					tableStones.add(stone);
				}
			}
		}
	}

	private void executeTurn() {
		if (turnDone) {
			return;
		}

		List<StoneSet> result = logic.getResult();

		if (result != null) {

			if (turnInfo.getLaidOut()) {
				doNotMoveExistingSets(result);
			}

			for (StoneSet set : result) {
				turnInfo.getTable().drop(
						set,
						new Position(10 * (Math.random() * 2 - 1), 5 * (Math
								.random() * 2 - 1)));
				for (Stone stone : set) {
					turnInfo.getHand().pickUp(stone);
				}
			}
		}

		endTurn();
	}

	private void doNotMoveExistingSets(List<StoneSet> result) {

		outerLoop: for (Iterator<Pair<StoneSet, Position>> it = turnInfo
				.getTable().iterator(); it.hasNext();) {
			Pair<StoneSet, Position> pair = it.next();
			setSearch: for (Iterator<StoneSet> it2 = result.iterator(); it2
					.hasNext();) {
				StoneSet set = it2.next();
				if (set.getSize() != pair.getFirst().getSize()) {
					continue;
				}
				for (int i = 0; i < set.getSize(); i++) {
					if (set.get(i) != pair.getFirst().get(i)) {
						continue setSearch;
					}
				}
				it2.remove();
				continue outerLoop;
			}
			it.remove();
		}
	}

	private void endTurn() {
		turnDone = true;

		long turnLength = System.currentTimeMillis() - startTime;

		if (useBackgroundThread) {
			Timer timer = new Timer(Math.max(0,
					(int) (1000 + Math.random() * 2000 - turnLength)),
					new ActionListener() {
						@Override
						public void actionPerformed(ActionEvent event) {
							emitEndOfTurn();
						}
					});
			timer.setRepeats(false);
			timer.start();
		} else {
			emitEndOfTurn();
		}

	}

	private void emitEndOfTurn() {
		readyToEmit = true;
		if (isPaused || aborted) {
			return;
		}
		cleanUp();
		endOfTurnEvent.emit(null, null, turnInfo.getTable());
	}

	/**
	 * Get the factory for the base AI control
	 * 
	 * @return the factory
	 */
	static public TurnControlFactory getFactory() {
		return new TurnControlFactory() {
			@Override
			public ITurnControl create() {
				return new AIControl();
			}
		};
	}

}
