package ServerRunner.Implementation;
import EnvironmentPluginAPI.Service.IEnvironmentConfiguration;
import EnvironmentPluginAPI.TransportTypes.TFactionChooser;
import ZeroTypes.Enumerations.ClientEventType;
import ZeroTypes.Enumerations.SessionStatus;
import EnvironmentPluginAPI.Exceptions.IllegalNumberOfClientsException;
import EnvironmentPluginAPI.Exceptions.TechnicalException;
import EnvironmentPluginAPI.Contract.IActionDescription;
import EnvironmentPluginAPI.Contract.IEnvironment;
import EnvironmentPluginAPI.Contract.IEnvironmentState;
import EnvironmentPluginAPI.Contract.TEnvironmentDescription;
import EnvironmentPluginAPI.Service.ICycleStatisticsSaver;
import EnvironmentPluginAPI.TransportTypes.TMARLAClientInstance;
import ZeroTypes.Interfaces.IHasTransportType;
import NetworkAdapter.Interface.Exceptions.ConnectionLostException;
import NetworkAdapter.Interface.Exceptions.NotConnectedException;
import NetworkAdapter.Interface.IServerNetworkAdapter;
import NetworkAdapter.Interface.MessageChannel;
import NetworkAdapter.Messages.CycleEndsMessage;
import NetworkAdapter.Messages.CycleStartsMessage;
import NetworkAdapter.Messages.SessionEndsMessage;
import NetworkAdapter.Messages.SessionStartsMessage;
import PluginLoader.Interface.Exceptions.PluginNotReadableException;
import PluginLoader.Interface.IEnvironmentPluginLoader;
import ServerRunner.Interface.IPlayerEventHandler;
import ServerRunner.Interface.SessionIsNotInReadyStateException;
import ZeroTypes.TransportTypes.TClientEvent;
import ZeroTypes.TransportTypes.TNetworkClient;
import ZeroTypes.TransportTypes.TSession;
import java.util.*;
/**
* This class manages a session of n clients that are contained in an own environment.
*/
class Session extends Thread implements IHasTransportType<TSession> {
// ------------------------------ FIELDS ------------------------------
private static List<IPlayerEventHandler> playerEventSubscribers = new ArrayList<IPlayerEventHandler>();
private UUID sessionId;
public UUID getSessionId() {
return sessionId;
}
private int numberOfPlayedGames = 0;
private String name;
private SessionStatus status;
public SessionStatus getStatus() {
return status;
}
private int numberOfGames;
private List<TNetworkClient> clientsInThisSession;
public List<TNetworkClient> getClientsInThisSession() {
return clientsInThisSession;
}
private HashMap<TMARLAClientInstance, TNetworkClient> clientsForPlayers;
private IEnvironment environment;
private final IEnvironmentConfiguration configuration;
private IEnvironmentPluginLoader environmentPluginLoader;
private IServerNetworkAdapter serverNetworkAdapter;
private IEnvironmentState currentEnvironmentState;
private IActionDescription actionsInTurn;
private TEnvironmentDescription environmentDescription;
private ICycleStatisticsSaver gameStatistics;
// -------------------------- STATIC METHODS --------------------------
static synchronized void addPlayerEventSubscriber(IPlayerEventHandler eventHandler) {
playerEventSubscribers.add(eventHandler);
}
// --------------------------- CONSTRUCTORS ---------------------------
public Session(TSession session,
IServerNetworkAdapter serverNetworkAdapterInstance,
IEnvironmentPluginLoader environmentPluginLoader,
ICycleStatisticsSaver saveGameStatistics)
throws TechnicalException, PluginNotReadableException {
this(session.getName(),
session.getNumberOfGames(),
session.getEnvironmentDescription(),
session.getConfiguration(),
session.getClientsInThisSession(),
serverNetworkAdapterInstance,
environmentPluginLoader,
saveGameStatistics);
}
public Session(String name,
int numberOfIterations,
TEnvironmentDescription environmentDescription,
IEnvironmentConfiguration configuration,
List<TNetworkClient> clientsInThisSession,
IServerNetworkAdapter serverNetworkAdapter,
IEnvironmentPluginLoader environmentPluginLoader,
ICycleStatisticsSaver gameStatistics)
throws TechnicalException, PluginNotReadableException {
super("Session");
this.name = name;
this.configuration = configuration;
this.environmentPluginLoader = environmentPluginLoader;
this.status = SessionStatus.READY;
this.numberOfGames = numberOfIterations;
this.clientsInThisSession = clientsInThisSession;
this.serverNetworkAdapter = serverNetworkAdapter;
this.environmentDescription = environmentDescription;
this.gameStatistics = gameStatistics;
clientsForPlayers = new HashMap<TMARLAClientInstance, TNetworkClient>();
sessionId = UUID.randomUUID();
}
// --------------------- GETTER / SETTER METHODS ---------------------
public void setActionsInTurn(IActionDescription actionsInTurn) {
this.actionsInTurn = actionsInTurn;
}
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface Runnable ---------------------
public void run() {
this.status = SessionStatus.RUNNING;
for (TNetworkClient networkClient : clientsInThisSession) {
clientsForPlayers.put(new TMARLAClientInstance(networkClient.getName(), networkClient.getId()), networkClient);
}
try {
environment = environmentPluginLoader.createEnvironmentInstance(gameStatistics);
environment.start(new ArrayList<TMARLAClientInstance>(clientsForPlayers.keySet()), configuration);
Thread.currentThread().setContextClassLoader(environmentPluginLoader.getUsedClassLoader());
currentEnvironmentState = environment.getCurrentEnvironmentState();
for (TNetworkClient networkClient : clientsInThisSession) {
serverNetworkAdapter.sendNetworkMessage(new SessionStartsMessage(networkClient.getId(), numberOfGames), MessageChannel.DATA);
}
while (numberOfPlayedGames < numberOfGames) {
doOneCycle();
numberOfPlayedGames += 1;
}
for (TNetworkClient networkClient : clientsInThisSession) {
serverNetworkAdapter.sendNetworkMessage(new SessionEndsMessage(networkClient.getId()), MessageChannel.DATA);
}
if (!status.equals(SessionStatus.FAILED)) {
this.status = SessionStatus.FINISHED;
sendPlayerEventMessage(new TClientEvent(ClientEventType.SessionEnded, this.getTransportType()));
} else {
sendPlayerEventMessage(new TClientEvent(ClientEventType.SessionFailedWithError, this.getTransportType()));
}
sendPlayerEventMessage(new TClientEvent(ClientEventType.SessionStarted, getTransportType()));
} catch (Exception e) {
e.printStackTrace();
status = SessionStatus.FAILED;
}
}
// -------------------------- PUBLIC METHODS --------------------------
public void UpdateSession(TSession session) throws SessionIsNotInReadyStateException {
if (!status.equals(SessionStatus.READY)) {
throw new SessionIsNotInReadyStateException();
}
this.numberOfGames = session.getNumberOfGames();
this.clientsInThisSession = session.getClientsInThisSession();
}
public List<TMARLAClientInstance> getPlayersInThisSession() {
return new ArrayList<TMARLAClientInstance>(clientsForPlayers.keySet());
}
// -------------------------- PRIVATE METHODS --------------------------
private void doOneCycle() throws NotConnectedException, ConnectionLostException, InterruptedException, TechnicalException {
try {
currentEnvironmentState = environment.start(new ArrayList<TMARLAClientInstance>(clientsForPlayers.keySet()), configuration);
} catch (IllegalNumberOfClientsException e) {
e.printStackTrace();
//TODO: Better exception handling, session should fail
}
for (TNetworkClient networkClient : clientsInThisSession) {
clientsForPlayers.put(new TMARLAClientInstance(networkClient.getName(), networkClient.getId()), networkClient);
IEnvironmentConfiguration conf;
if(clientsInThisSession.indexOf(networkClient) == 0){
conf = new TFactionChooser("RED");
} else {
conf = new TFactionChooser("BLUE");
}
serverNetworkAdapter.sendNetworkMessage(new CycleStartsMessage(networkClient.getId(), conf), MessageChannel.DATA);
}
sendCurrentEnvironmentStateToClient(clientsForPlayers.get(environment.getActiveInstance()));
synchronized (this) {
// TODO: A timeout mechanic would be nice, if the clients do not respond.
wait();
}
advanceTurns();
sendPlayerEventMessage(new TClientEvent(ClientEventType.CycleStarted, this.getTransportType()));
}
private void sendCurrentEnvironmentStateToClient(TNetworkClient networkClient) throws NotConnectedException, ConnectionLostException {
serverNetworkAdapter.sendNetworkMessage(environmentPluginLoader.createEnvironmentStateMessage(networkClient.getId(), currentEnvironmentState), MessageChannel.DATA);
}
private void advanceTurns() throws NotConnectedException, ConnectionLostException, InterruptedException, TechnicalException {
synchronized (this) {
while (status.equals(SessionStatus.RUNNING)) {
currentEnvironmentState = environment.executeAction(actionsInTurn);
if (environment.isStillActive()) {
sendCurrentEnvironmentStateToClient(clientsForPlayers.get(environment.getActiveInstance()));
this.wait();
} else {
endCycle();
break;
}
}
}
}
private void endCycle() throws NotConnectedException, ConnectionLostException, InterruptedException, TechnicalException {
environment.end();
for (TMARLAClientInstance player : clientsForPlayers.keySet()) {
CycleEndsMessage message;
if (environment.getActiveInstance().equals(player)) {
message = new CycleEndsMessage(true, clientsForPlayers.get(player).getId());
} else {
message = new CycleEndsMessage(false, clientsForPlayers.get(player).getId());
}
serverNetworkAdapter.sendNetworkMessage(message, MessageChannel.DATA);
}
sendPlayerEventMessage(new TClientEvent(ClientEventType.CycleEnded, this.getTransportType()));
}
static synchronized void sendPlayerEventMessage(TClientEvent event) {
for (IPlayerEventHandler eventHandler : playerEventSubscribers) {
eventHandler.call(event);
}
}
@Override
public TSession getTransportType() {
return new TSession(sessionId, name, status, configuration, clientsInThisSession.size(), numberOfGames, clientsInThisSession, environmentDescription);
}
}