package net.woodyfolsom.msproj;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import net.woodyfolsom.msproj.gui.Goban;
import net.woodyfolsom.msproj.policy.HumanGuiInput;
import net.woodyfolsom.msproj.policy.HumanKeyboardInput;
import net.woodyfolsom.msproj.policy.MonteCarloUCT;
import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.RandomMovePolicy;
import net.woodyfolsom.msproj.policy.RootParallelization;

public class StandAloneGame {
	private static final int DEFAULT_TURN_LENGTH = 2; // default turn is 2
														// seconds;

	private static final double DEFAULT_KOMI = 5.5;
	private static final int DEFAULT_NUM_GAMES = 1;
	private static final int DEFAULT_SIZE = 9;

	enum PLAYER_TYPE {
		HUMAN, HUMAN_GUI, ROOT_PAR, UCT_FAST, UCT_SLOW, RANDOM
	};

	public static void main(String[] args) {
		if (args.length != 3) {
			System.out
					.println("Incorrect # of arguments: use StandAloneGame <Player1Type> <Player2Type> <# rounds>");
			System.out
					.println("For example to play 10 games against MC UCT w/ slow moves: StandAloneGame UCT_SLOW HUMAN 10");
		}
		int nGames = DEFAULT_NUM_GAMES;
		int size = DEFAULT_SIZE;
		double komi = DEFAULT_KOMI;

		switch (args.length) {
		case 5:
			nGames = Integer.valueOf(args[4]);
		case 4:
			komi = Double.valueOf(args[3]);
		case 3:
			size = Integer.valueOf(args[2]);
			break;
		default:
			System.out
					.println("Arguments #3-5 not specified.  Using default size="
							+ size
							+ ", komi = "
							+ komi
							+ ", nGames="
							+ nGames
							+ ".");
		}
		new StandAloneGame().playGame(parsePlayerType(args[0]),
				parsePlayerType(args[1]), size, komi, nGames);
	}

	private static PLAYER_TYPE parsePlayerType(String playerTypeStr) {
		if ("UCT_FAST".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.UCT_FAST;
		} else if ("ROOT_PAR".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.ROOT_PAR;
		} else if ("UCT_SLOW".equalsIgnoreCase(playerTypeStr)
				|| "UCT".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.UCT_SLOW;
		} else if ("HUMAN".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.HUMAN;
		} else if ("HUMAN_GUI".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.HUMAN_GUI;
		} else if ("RANDOM".equalsIgnoreCase(playerTypeStr)) {
			return PLAYER_TYPE.RANDOM;
		} else {
			throw new RuntimeException("Unknown player type: " + playerTypeStr);
		}
	}

	public void playGame(PLAYER_TYPE playerType1, PLAYER_TYPE playerType2,
			int size, double komi, int rounds) {

		GameConfig gameConfig = new GameConfig(size);
		gameConfig.setKomi(komi);

		Referee referee = new Referee();
		referee.setPolicy(Player.BLACK,
				getPolicy(playerType1, gameConfig, Player.BLACK));
		referee.setPolicy(Player.WHITE,
				getPolicy(playerType2, gameConfig, Player.WHITE));

		List<GameResult> round1results = new ArrayList<GameResult>();

		for (int round = 0; round < rounds; round++) {
			round1results.add(referee.play(gameConfig));
		}

		List<GameResult> round2results = new ArrayList<GameResult>();

		referee.setPolicy(Player.BLACK,
				getPolicy(playerType2, gameConfig, Player.BLACK));
		referee.setPolicy(Player.WHITE,
				getPolicy(playerType1, gameConfig, Player.WHITE));
		for (int round = 0; round < rounds; round++) {
			round2results.add(referee.play(gameConfig));
		}

		DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssZ");

		try {

			File txtFile = new File("gotournament-"
					+ dateFormat.format(new Date()) + ".txt");
			FileWriter writer = new FileWriter(txtFile);

			try {
				logResults(writer, round1results, playerType1.toString(),
						playerType2.toString());
				logResults(writer, round2results, playerType2.toString(),
						playerType1.toString());

				System.out.println("Game tournament saved as "
						+ txtFile.getAbsolutePath());
			} finally {
				try {
					writer.close();
				} catch (IOException ioe) {
					ioe.printStackTrace();
				}
			}
		} catch (IOException ioe) {
			System.out.println("Unable to save game file due to IOException: "
					+ ioe.getMessage());
		}

	}

	private void logResults(FileWriter writer, List<GameResult> results,
			String player1, String player2) throws IOException {
		String header = "Cumulative results for " + results.size()
				+ " games (BLACK=" + player1 + ", WHITE=" + player2 + ")";

		System.out.println(header);
		writer.write(header);
		writer.write("\n");

		for (int i = 0; i < results.size(); i++) {
			String resultLine = (i + 1) + ". " + results.get(i);
			System.out.println(resultLine);
			writer.write(resultLine);
			writer.write("\n");
		}
		writer.flush();
	}

	private Policy getPolicy(PLAYER_TYPE playerType, GameConfig gameConfig,
			Player player) {
		switch (playerType) {
		case HUMAN:
			return new HumanKeyboardInput();
		case HUMAN_GUI:
			return new HumanGuiInput(new Goban(gameConfig, player));
		case ROOT_PAR:
			return new RootParallelization(4, DEFAULT_TURN_LENGTH * 1000L * 3);
		case UCT_SLOW:
			return new MonteCarloUCT(new RandomMovePolicy(),
					DEFAULT_TURN_LENGTH * 1000L * 3);
		case UCT_FAST:
			return new MonteCarloUCT(new RandomMovePolicy(),
					DEFAULT_TURN_LENGTH * 1000L);
		case RANDOM:
			return new RandomMovePolicy();
		default:
			throw new IllegalArgumentException("Invalid PLAYER_TYPE: "
					+ playerType);
		}
	}
}
