package admin;

import java.io.File;
import java.io.IOException;

import config.AdminConfig;
import config.GlobalConfig;
import edu.neu.ccs.demeterf.demfgen.lib.List;
import edu.neu.ccs.demeterf.demfgen.lib.List.Pred;
import gen.*;
import tournament.Result;
import tournament.Tournament;
import admin.utils.*;
import admin.utils.Game.GameType;
import utils.comparator.LargerBalance;

public class TournAdmin extends Administrator
{
	public TournAdmin(Output o) { super(o, GlobalConfig.DEFAULT_MAX_STACK); }
	
	/** Run a match between white and black. */
	@SuppressWarnings("unchecked")
	public Result runMatch(Player white, Player black, GameType gt)
	{
		Config config = AdminDocumentHandler.getConfig();
		Store store = AdminDocumentHandler.getStore();
		Pair<PlayerID, Double> whiteAcct = new Pair<PlayerID, Double>(white.id, config.Money);
		Pair<PlayerID, Double> blackAcct = new Pair<PlayerID, Double>(black.id, config.Money);
		List<Pair<PlayerID, Double>> accts = List.<Pair<PlayerID, Double>>create();
		accts = accts.append(whiteAcct);
		accts = accts.append(blackAcct);
		Accounts accounts = new Accounts(accts);
		AdminDocumentHandler.commitAccounts(accounts);
		for(int roundNum = 1; roundNum <= 2 + config.Rounds; roundNum++)
		{
			if(overtime(roundNum)) { System.out.println("OVERTIME ROUND: " + (roundNum - config.Rounds)); }
			else { System.out.println("ROUND: " + roundNum); }
			List<PlayerTransaction> pTransactions = List.create();
			Round round = new Round(roundNum, pTransactions);
			if(!passTurn(white, config)) { return Result.black("White time violation or jar not found."); }
			PlayerTransaction whitePTrans = AdminDocumentHandler.getPlayerTrans(white); 	
			if(overtime(roundNum))
			{
				whitePTrans.transactions = whitePTrans.transactions.filter(new Pred<Transaction>(){
					public boolean huh(Transaction arg0) {
						return arg0.ttype instanceof Deliver || arg0.ttype instanceof Finish;
					}
                });
			}
			if(whitePTrans == null)
			{ 
				return Result.black("No white PlayerTrans.");
			}
			else 
			{
				Pair<Boolean, String> result;
				if(overtime(roundNum)) { result = RuleChecker.overtimeRulesFollowed(whitePTrans, store, accounts, gt, config); }
				else { result = RuleChecker.rulesFollowed(whitePTrans, store, accounts, gt, config); }
				
				if(!result.a) { return Result.black(result.b); }
				else{
					whitePTrans = Util.computeQualities(whitePTrans);
                    whitePTrans = Util.truncateRMs(whitePTrans, config.MaxRawMaterialLen);
		            store = this.updateStore(whitePTrans, store);
		            accounts = this.updateAccounts(whitePTrans, accounts);
				}
			}
			if(!passTurn(black, config)) { return Result.white("Black time violation or jar not found."); }
			PlayerTransaction blackPTrans = AdminDocumentHandler.getPlayerTrans(black);
			if(overtime(roundNum)){
				blackPTrans.transactions = blackPTrans.transactions.filter(new Pred<Transaction>(){
					public boolean huh(Transaction arg0) {
						return arg0.ttype instanceof Deliver || arg0.ttype instanceof Finish;
					}
                });
			}
			if(blackPTrans == null){ 
				return Result.white("No black PlayerTrans.");
			}
			else{
				Pair<Boolean, String> result;
				if(overtime(roundNum)) { result = RuleChecker.overtimeRulesFollowed(blackPTrans, store, accounts, gt, config); }
				else { result = RuleChecker.rulesFollowed(blackPTrans, store, accounts, gt, config); }
				
				if(!result.a) { return Result.white(result.b); }
				else{
                    blackPTrans = Util.computeQualities(blackPTrans);
                    blackPTrans = Util.truncateRMs(blackPTrans, config.MaxRawMaterialLen);
		            store = this.updateStore(blackPTrans, store);
		            accounts = this.updateAccounts(blackPTrans, accounts);
				}
			}
			AdminDocumentHandler.updateHistory(round);
		}
		Accounts finalAccts = AdminDocumentHandler.getAccounts();
		List<Pair<PlayerID, Double>> sortedAccts = finalAccts.accounts.sort(new LargerBalance());
		PlayerID winner = sortedAccts.top().a;
		if(winner.id == white.id.id) { return Result.white("Larger balance: " + sortedAccts.top().b); }
		else { return Result.black("Larger balance: " + sortedAccts.top().b); }
	}
	
	/** Updates the store with the player transaction */
    private Store updateStore(PlayerTransaction trans, Store existingStore) {
        existingStore = StoreUpdater.updateStore(existingStore, trans);
        AdminDocumentHandler.commitStore(existingStore);
        return existingStore;
    }
    
    /** Updates the accounts with the player transaction */
    private Accounts updateAccounts(PlayerTransaction pTrans, Accounts accounts) {
        accounts = AccountUpdater.updateAccounts(accounts, pTrans);
        AdminDocumentHandler.commitAccounts(accounts);  
        return accounts;
    }
	
	/** Passes the turn to player */
    private boolean passTurn(Player player, Config config) {
    	File playerDir = new File(AdminConfig.BLACKBOARD_PATH + AdminConfig.PLAYERS_PATH);
        System.out.println("Player " + player.id.id + " of team " + player.name + "'s turn.");
        // Determine if the player is C# or Java
        String cmdline;
        String fName = AdminConfig.BLACKBOARD_PATH + AdminConfig.PLAYERS_PATH + "/" + player.name;
        String jcmdline = "java -Xss" + GlobalConfig.DEFAULT_MAX_STACK + " -jar " + player.name + ".jar " + player.id.id + " " + player.name;
        String ccmdline = fName+ ".exe " + player.id.id + " " + player.name;
        File file;
        file = new File(fName + ".jar");
        if (file.exists()){ cmdline = jcmdline; } 
        else{
            file = new File(fName + ".exe");
            if (file.exists()){
                cmdline = ccmdline;
            } else {
                return false;
            }
        }
        Process p;
        Runtime r = Runtime.getRuntime();
        long sTime = 0, eTime = 0;
        try{
            sTime = System.currentTimeMillis();
            p = r.exec(cmdline, null, playerDir);
            StreamWatcher outWatch = new StreamWatcher(p.getInputStream(), Output.htmlOutput());
            StreamWatcher errWatch = new StreamWatcher(p.getErrorStream(), Output.htmlOutput());
            ProcessWatcher pWatch = new ProcessWatcher(p);
            Thread out = new Thread(outWatch);
            Thread err = new Thread(errWatch);
            Thread timer = new Thread(pWatch);
            out.start();
            err.start();
            timer.start();
            pWatch.waitForTimeLimit(config.Time);
            Thread.sleep(2000);
            System.out.flush();
            System.err.flush();
            eTime = System.currentTimeMillis();
            outWatch.kill();
            errWatch.kill();
            System.out.println("Player " + player.id.id + " of team " + player.name + " finished in " + (eTime - sTime) + " milliseconds.");
            if ((eTime - sTime) > config.Time){
                p.destroy();
                System.out.println("Player " + player.id.id + " kicked for time violation.");
                return false;
            }
        }
        // Catch for p = r.exec(cmdline);
        catch(IOException e) { e.printStackTrace(); } 
        // Catch for pWatch.waitForTimeLimit(config.Time);
        catch (InterruptedException e) { e.printStackTrace(); }
        return true;
    }
    
    public static void main(String[] args)
	{
    	AdminCmdLineParser cmds = new AdminCmdLineParser(args);
		Players p = AdminDocumentHandler.getPlayers();
		Tournament t = new Tournament(p.players, cmds.outputFile, cmds.gt);
		tournament.TournHTMLOutput o = tournament.TournHTMLOutput.tournhtmlOutput(cmds.resultsFile);
		o.header();
		t.runTournament();
		o.finishTable(t.getResults());
		o.footer();
	}
}