import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class FortSrv {
	public final int majversion = 0;
	public final int minversion = 0;
	public final int microversion = 1;
	
	public final int ALLPLAYERS = -1;
	
	private String servername;
	private int serverport;
	private int numplayers;
	private boolean allowwatchers;

	private boolean quit;	
	
	private Vector<Player> observers;
	private LinkedBlockingQueue<FMessage>[] messagequeues;

	private Vector<District> carddeck;
	
	private TurnManager tm = null;
	
	public String getVersion() {
    	return String.valueOf( majversion ) + "." +
		       String.valueOf( minversion ) + "." +
			   String.valueOf( microversion );
	}

	public String getName() {
		return servername;
	}
	
	public String getPlayerName( int i ) {
		return tm.getPlayerName( i );
	}
	
	public int getPlayerIndex( String name ) {
		return tm.getPlayerIndex( name );
	}
	
	public boolean playerExists( int idx ) {
		return tm.playerExists( idx );
	}
	
	public boolean sendMessage( FMessage msg ) {
		if (msg.broadcast) {
			for (Player p : observers) {
				p.sendMessage( msg );
			}
		} else {
			if (tm.playerExists( msg.playerto )) {
				try {
					(tm.getPlayer( msg.playerto )).sendMessage( msg );
				} catch (ArrayIndexOutOfBoundsException e) {
					// Probably a synchronization error. Not serious enough to warrant coding for.
					System.out.println( "Message failed sending to " + msg.playerto + "." );
					return false;
				}
			} else {
				System.out.println( "Player " + msg.playerto + " not connected." );
				return false;
			}
		}
		
		return true;
	}
	
	public void done() {
		quit = true;
	}
	
	public String serverInfo() {
		return "Fortresses server '" + getName() + "' version " + getVersion();
	}
	
	public String protocolHelp( String topic ) {
		return Help.protocolHelp( topic );
	}
	
	private void shuffleDistricts() {
		Random r = new Random();
		int n = carddeck.size();
		
		if (n > 0) {
			Vector<District> newdeck = new Vector<District>( n );

			while (n > 0) {
				newdeck.add( carddeck.remove( r.nextInt( n ) ) );
				n--;
			}

			carddeck = newdeck;
		}
	}
	
	private void initializeDistricts( String filename ) {
		BufferedReader cards;
		String instr;
		
		carddeck = new Vector<District>();
		
		try {
			cards = new BufferedReader( new FileReader( filename ) );
			while ((instr = cards.readLine()) != null) {
				if (! (instr.startsWith( "#" ) ||
				       instr.trim().equals( "" ))) {
					carddeck.add( new District( instr ) );
				}
			}
			cards.close();

			System.out.println( carddeck.size() + " district cards initialized." );
		} catch (IOException e) {
			System.out.println( "Error reading district file: " + e.getMessage() );
		}
		
		shuffleDistricts();
	}
	
	public District drawDistrict() throws EmptyDeckException {
		if (carddeck.isEmpty()) {
			throw new EmptyDeckException();
		}
		
		return carddeck.remove( 0 );
	}
	
	public Vector<District> drawDistricts( int num ) throws EmptyDeckException {
		Vector<District> drawpile = new Vector<District>( num );
		
		for (int i = 0; i < num; i++) {
			drawpile.add( drawDistrict() );
		}
		
		return drawpile;
	}
	
	public void discardDistrict( District d ) {
		carddeck.add( d );
	}
	
	public FortSrv( String name, int port, int nplayers, boolean watchers ) {
		servername = name;
		serverport = port;
		numplayers = nplayers;
		allowwatchers = watchers;
		
		int playercount = 0;
		
		ConnectionServer cs;
		
		observers = new Vector<Player>();
		initializeDistricts( "districts.dat" );
		tm = new TurnManager( observers, numplayers );
		tm.start();
		try {
			cs = new ConnectionServer( port );
			System.out.println( "Setting up game '" + name + "' on Fortresses server with " +
		                    	nplayers + " players running on port " + port + ".\n" +
								"Waiting for players to connect..." );
			
			quit = false;
			while (!quit) {
				Player p = new Player( cs.newConnection(), this, ++playercount, drawDistricts( 4 ) );
				p.start();
				observers.add( p );
			}
			cs.close();
		} catch (IOException e) {
			System.out.println( "Cannot start server.\nReason: " + e.getMessage() );
		} catch (SecurityException e) {
			System.out.println( "Cannot start server.\nReason: " + e.getMessage() );
		} catch (EmptyDeckException e) {
			System.out.println( "Ran out of district cards.\nPlease check 'districts.dat' to ensure enough cards are defined." );
		}
	}
	
	public String playerFlags( int player ) {
		return tm.playerFlags( player );
	}
	
	public String playerStatus( int player ) {
		if (tm.playerExists( player )) {
			try {
				return "Player index: " + player + "\n" + (tm.getPlayer( player )).getStatus();
			} catch (ArrayIndexOutOfBoundsException e) {
				return "Player " + player + " not connected.";
			}
		} else {
			return "Player " + player + " not connected.";
		}
	}
	
	public int getIndex( Player p ) {
		return tm.getIndex( p );
	}
}
