package jp.ac.nii.icpc2010.manager;

import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Vector;

import jp.ac.nii.icpc2010.TronException;
import jp.ac.nii.icpc2010.playfield.FieldDirection;
import jp.ac.nii.icpc2010.playfield.PlayField;
import jp.ac.nii.icpc2010.recorder.FileRecorder;
import jp.ac.nii.icpc2010.recorder.IRecorder;
import jp.ac.nii.icpc2010.score.AbstractScore;


public class GameManager implements Runnable
{
	private PlayField _field;

	private PlayerManager _manager;

	private OptionsManager _options;
	private boolean _initialized;

	public GameManager(PlayField field, OptionsManager om) throws TronException
	{
		_field = field;
		_initialized = false;
		_options = om;
	}


	public void run()
	{		
		int startTurn = 0;
		
		String[] playerNames = new String[_field.getNumOfPlayers()];

		String recordFile = _options.getRecordFile();
		IRecorder recorder = null;
		if (recordFile != null) {
			recorder = new FileRecorder(playerNames, recordFile, _options);
			if (!_options.isTournamentMode()) {
				System.out.println("Recording to " + recordFile);
			}
		}
		
		for(int i = 0; i < _options.getGameRounds(); i++){
			try {
				if(_initialized){
					_field.reset(_options.getLevelName());
				
				}else{
					_field.init(_options.getLevelName());
					_initialized = true;
				
				}
				_field.putComment("--- Round " + _field.getRound() + " starts ---");
				if (i == 0) {
					_manager = new PlayerManager(_field, recorder, _options);
				} else {
					_manager = new PlayerManager(_field, recorder, startTurn, _options);
				}
			} catch (IOException e) {
				e.printStackTrace();
				System.exit(1);
			} catch (TronException e) {
				e.printStackTrace();
				System.exit(1);
			}

			onePlay();
			startTurn = _manager.getStartTurn();
		}
		
		ScorePack[][] result = getResult(_field.getTotalScores());

		double[] score = new double[2];
		_field.putComment("--- Total result ---");
		for(int i = 0; i < result.length; i++){
			for(int j = 0; j < result[i].length; j++){
				_field.putComment(String.format("%s. Player %d: %.8f", toOrderString(i + 1), result[i][j].playerId, result[i][j].score.getValue()));
				score[result[i][j].playerId] = result[i][j].score.getValue();
			}
		}
		try {
			if (recorder != null) {
				recorder.addPlayerScore(score[0]);
				recorder.addPlayerScore(score[1]);
			}
		} catch (TronException e) {
			e.printStackTrace();
			System.exit(1);
		}				

		_manager.finishRecording();
	}


	private void onePlay(){
		while(true)
		{
			if (_field.isRunning()) {
				long start = System.currentTimeMillis();

				FieldDirection[] inputs = _manager.getInputs();
				_field.setKeyPressed(null);

				_field.update(inputs);

				
				if (!_options.isTournamentMode()) {
					//if tournament mode, ignore the frame rate setting ("timeslot") and just run as fast as we can.
					long sleepTime = _options.getTurnTimeslot() - (System.currentTimeMillis() - start);

					if(sleepTime > 0){
						try
						{
							Thread.sleep(sleepTime);
						}
						catch(Exception e)
						{
							e.printStackTrace();
							System.exit(1);
						}
					}
				}
			}
			else {

				ScorePack[][] result = getResult(_field.getScores());

				_field.putComment("--- Round " + _field.getRound() + " result ---");
				for(int i = 0; i < result.length; i++){
					for(int j = 0; j < result[i].length; j++){
						_field.putComment(String.format("%s. Player %d: %.8f", toOrderString(i + 1), result[i][j].playerId, result[i][j].score.getValue()));
					}
				}

				int winnerId = result[0][(int) (Math.random() * result[0].length)].playerId;
				int loserId = result[result.length - 1][(int) (Math.random() * result[result.length - 1].length)].playerId;

				QuoteManager.Singleton().setWinner(_field.getPlayerName(winnerId));
				QuoteManager.Singleton().setLoser(_field.getPlayerName(loserId));

				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.exit(1);
				}
				break;
			}
		}
	}

	private static String toOrderString(int n){
		switch(n % 10){
		case 1:
			if(n == 11){
				return "11th";
			}else{
				return n + "st";
			}
		case 2:
			return n + "nd";
		case 3:
			return n + "rd";
		default:
			return n + "th";
		}
	}

	private class ScorePack implements Comparable<ScorePack>{
		public int playerId;
		public AbstractScore score;

		public ScorePack(int playerId, AbstractScore score){
			this.playerId = playerId;
			this.score = score;
		}

		public int compareTo(ScorePack scorePack){
			return this.score.compareTo(scorePack.score);
		}
	}

	private ScorePack[][] getResult(AbstractScore[] scores){
		ScorePack[] scorePacks = new ScorePack[scores.length];
		for(int i = 0; i < scores.length; i++){
			scorePacks[i] = new ScorePack(i, scores[i]);
		}
		Arrays.sort(scorePacks);

		Vector<LinkedList<ScorePack>> tmp = new Vector<LinkedList<ScorePack>>();
		int order = 0;
		AbstractScore curScore = null;
		for(int i = scorePacks.length - 1; i >= 0; i--){
			AbstractScore score = scorePacks[i].score;
			if(curScore == null || curScore.compareTo(score) > 0){
				curScore = score;
				order = scorePacks.length - 1 - i;
				while(tmp.size() < order + 1){
					tmp.add(new LinkedList<ScorePack>());
				}
				tmp.get(order).addFirst(scorePacks[i]);
			}else{
				tmp.get(order).addFirst(scorePacks[i]);
			}
		}

		ScorePack[][] result = new ScorePack[tmp.size()][];
		for(int i = 0; i < tmp.size(); i++){
			result[i] = tmp.get(i).toArray(new ScorePack[tmp.get(i).size()]);
		}

		return result;
	}
}
