package jp.ac.nii.icpc2010.manager;

import java.lang.reflect.Constructor;
import java.util.Vector;
import java.util.concurrent.TimeoutException;

import jp.ac.nii.icpc2010.Tron;
import jp.ac.nii.icpc2010.TronException;
import jp.ac.nii.icpc2010.players.AbstractPlayer;
import jp.ac.nii.icpc2010.playfield.FieldDirection;
import jp.ac.nii.icpc2010.playfield.IPlayField;
import jp.ac.nii.icpc2010.playfield.PlayField;
import jp.ac.nii.icpc2010.playfield.RefPlayField;
import jp.ac.nii.icpc2010.recorder.FileRecorder;
import jp.ac.nii.icpc2010.recorder.IRecorder;
import jp.ac.nii.icpc2010.recorder.PlayerEvent;
import jp.ac.nii.icpc2010.recorder.ReplayPlayer;



public class PlayerManager
{
	private PlayField _field;

	private Vector<AbstractPlayer> _players;
	private TimeKeeper _timeKeeper;

	private OptionsManager _options;
	
	private IRecorder _recorder;
	private IRecorder _playbackRecorder;
	
	
	private int _startTurn;
	
	public PlayerManager(PlayField field, IRecorder recorder, OptionsManager om) throws TronException
	{
		
		this(field, recorder, 0, om);	
	}
	
	public PlayerManager(PlayField field, IRecorder recorder, int startTurn, OptionsManager om) throws TronException {
		_field = field;
		_recorder = recorder;
		_options = om;
		
		_players = new Vector<AbstractPlayer>();
		new Vector<String>();

		_timeKeeper = new TimeKeeper();
		
		_startTurn = startTurn;
		initialize();
	}

	@SuppressWarnings("unchecked")
	private void initialize() throws TronException
	{
		_players.clear();
		String[] playerNames = new String[_field.getNumOfPlayers()];

	
				
		String playbackFile = _options.getPlaybackFile();
		if (playbackFile != null) {
			System.out.println("Playback from " + playbackFile);
			_playbackRecorder = new FileRecorder(playerNames, playbackFile, _options);
			_playbackRecorder.load();
		}
		
	
		
		for (int i = 0; i < _field.getNumOfPlayers(); i++){
			String playerClassName = _field.getPlayerName(i);
			playerNames[i] = playerClassName;

			Object obj;
			try {

				RefPlayField ref = new RefPlayField(_field.getWidth(), _field.getHeight(), _players.size());
				ref.copy(_field);

				
				if (_playbackRecorder != null) {
					obj = new ReplayPlayer(i, ref, _playbackRecorder.getEvents(), _startTurn);
					_players.add((AbstractPlayer) obj);

				} else {
					
					String tronClass = "";
					if (playerClassName.contains(".")) // has absolute package path
						tronClass = playerClassName;
					else  // seems to have relative path
						tronClass = "jp.ac.nii.icpc2010.players." + playerClassName;

					Constructor<? extends AbstractPlayer> constructor = (Constructor<? extends AbstractPlayer>) Class.forName(tronClass).getConstructor(
							new Class[]{int.class, IPlayField.class});
					AbstractPlayer player = null;
					try{
						//player = _timeKeeper.execute(new PlayerInit(constructor, i, ref), OptionsManager.Singleton().getInitTimeout());
						player = _timeKeeper.getInstance(constructor, i, ref, _options.getInitTimeout(), _options);
					}catch(TimeoutException e){
						_field.putComment("Player " + i + " dies: initilization time out");					
						_field.getTron(i).die();
					}catch(Exception e){
						e.printStackTrace();
						_field.getTron(i).die();									
					}
					_players.add(player);
				}

			} catch (Exception e) {
				e.printStackTrace();
				System.exit(1);
			}
		}
	}

	// if timeout <= 0, no time limit.
	public FieldDirection[] getInputs(){
		long timeout = _options.getRunTimeout();
		TimeoutAction timeoutAction = _options.getTimeoutAction();;

		RefPlayField ref = new RefPlayField(_field.getWidth(), _field.getHeight(), _players.size());
		ref.copy(_field);

		FieldDirection[] inputs = new FieldDirection[_players.size()];
		PlayerEvent[] events = new PlayerEvent[_players.size()];
		
		for(int i = 0; i < _players.size(); i++)
		{
			
			Tron tron = _field.getTron(i);
			if(tron.isAlive()){
				
				
				AbstractPlayer player = _players.get(i);
				player.setPlayField(ref);

				if(timeout > 0){
					try{
						inputs[i] = _timeKeeper.getInput(player, timeout);
						events[i] = PlayerEvent.fromFieldDirection(inputs[i]);						
					}catch(TimeoutException e){
						//System.out.println("Time out: " + i);
						
						//Have to add 1 here to get the correct turn numbering.
						_field.putComment("[" + (_field.getTurn() + 1) + "] Player " + i + " timed out");
						if(timeoutAction == TimeoutAction.Die){
							inputs[i] = FieldDirection.reverse(tron.getDir());
						}else{
							inputs[i] = tron.getDir();
						}
						events[i] = PlayerEvent.Timeout;
						
					}catch(Exception e){
						e.printStackTrace();
						inputs[i] = FieldDirection.reverse(tron.getDir());
					}
				}else{
					inputs[i] = player.getInput();
					events[i] = PlayerEvent.fromFieldDirection(inputs[i]);
				}
			}else{
				inputs[i] = null;
				events[i] = null;
			}
		}

		_startTurn++;
		if (_recorder != null) {
			try {
				_recorder.addEvents(events);
			} catch (TronException te) {
				System.err.println("Error while recording events");
				te.printStackTrace();
			}
		}
		
		return inputs;
	}
	
//	public void finalize(){
//		_timeKeeper.finalize();
//		return;
//	}

	int getStartTurn() {
		return _startTurn;
	}
	
	public int getPlayerSize(){
		return _players.size();
	}
	
	public void finishRecording() {
		if (_recorder != null) {
			try {
				_recorder.closeWriting();
			} catch (TronException e) {
				System.err.println("Error while recording events");
				e.printStackTrace();
			}
		}
	}
}