package com.apixandru.rummikub.server;

import com.apixandru.rummikub.api.room.RummikubRoomListener;
import com.apixandru.rummikub.model.Rummikub;
import com.apixandru.rummikub.model.RummikubFactory;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static com.apixandru.rummikub.server.RummikubException.Reason.NAME_TAKEN;
import static com.apixandru.rummikub.server.RummikubException.Reason.NO_NAME;
import static com.apixandru.rummikub.server.RummikubException.Reason.ONGOING_GAME;

/**
 * @author Alexandru-Constantin Bledea
 * @since April 10, 2016
 */
public class RummikubImpl implements RummikubRoomConfigurer {

    private final Map<String, StateChangeListener> players = new HashMap<>();

    private final Map<String, RummikubRoomListener> waitingRoomListeners = new LinkedHashMap<>();

    private Rummikub<Integer> rummikubGame;

    private boolean inProgress;

    private static boolean isEmpty(final String string) {
        return null == string || string.isEmpty();
    }

    void validateCanJoin(final String playerName) {
        if (isEmpty(playerName)) {
            throw new RummikubException(NO_NAME);
        }
        if (players.containsKey(playerName)) {
            throw new RummikubException(NAME_TAKEN);
        }
        if (inProgress) {
            throw new RummikubException(ONGOING_GAME);
        }
    }

    void addPlayer(final String playerName, final StateChangeListener listener) {
        listener.enteredWaitingRoom(this);
        notifyOfPreviouslyJoinedPlayers(playerName);
        players.put(playerName, listener);
        broadcastPlayerJoined(playerName);
    }

    private void notifyOfPreviouslyJoinedPlayers(String newPlayer) {
        RummikubRoomListener listener = waitingRoomListeners.get(newPlayer);
        for (String playerName : players.keySet()) {
            listener.playerJoined(playerName);
        }
    }

    private void broadcastPlayerJoined(final String playerName) {
        waitingRoomListeners
                .values()
                .forEach(waitingRoomListener -> waitingRoomListener.playerJoined(playerName));
    }

    private void broadcastPlayerLeft(final String playerName) {
        waitingRoomListeners
                .values()
                .forEach(waitingRoomListener -> waitingRoomListener.playerLeft(playerName));
    }

    @Override
    public void startGame() {
        rummikubGame = RummikubFactory.newInstance();
        players.values()
                .forEach(listener -> listener.enteredGame(rummikubGame));
        inProgress = true;
    }

    @Override
    public void registerListener(String playerName, final RummikubRoomListener listener) {
        waitingRoomListeners.put(playerName, listener);
    }

    @Override
    public void unregisterListener(RummikubRoomListener listener) {
        waitingRoomListeners
                .values()
                .remove(listener);
    }

    void unregister(final String playerName) {
        players.remove(playerName); // TODO synchronize deez!
        if (!inProgress) {
            broadcastPlayerLeft(playerName);
        }
    }

}

@@ -273,16 +273,43 @@
 til.Map;
+%0Aimport java.util.Optional;
 %0A%0Aimport
@@ -1544,401 +1544,43 @@
     
-notifyOfPreviouslyJoinedPlayers(playerName);%0A        players.put(playerName, listener);%0A        broadcastPlayerJoined(playerName);%0A    %7D%0A%0A    private void notifyOfPreviouslyJoinedPlayers(String newPlayer) %7B%0A        RummikubRoomListener listener = waitingRoomListeners.get(newPlayer);%0A        for (String playerName : players.keySet()) %7B%0A            listener.playerJoined(playerName);%0A        %7D
+players.put(playerName, listener);%0A
 %0A   
@@ -2353,32 +2353,83 @@
 ner listener) %7B%0A
+        notifyOfPreviouslyJoinedPlayers(listener);%0A
         waitingR
@@ -2476,59 +2476,102 @@
     
-%7D%0A%0A
     
-@Override%0A    public void unregisterListen
+broadcastPlayerJoined(playerName);%0A    %7D%0A%0A    private void notifyOfPreviouslyJoinedPlay
 er
+s
 (Rum
@@ -2599,32 +2599,73 @@
 tener) %7B%0A       
+ for (String previouslyJoinedPlayerName :
  waitingRoomList
@@ -2661,32 +2661,44 @@
 ingRoomListeners
+.keySet()) %7B
 %0A               
@@ -2690,37 +2690,74 @@
             
-    .values()
+listener.playerJoined(previouslyJoinedPlayerName);
 %0A           
@@ -2757,24 +2757,87 @@
     
+%7D%0A
     
+%7D%0A%0A
     
-.remove(
+@Override%0A    public void unregisterListener(RummikubRoomListener 
 list
@@ -2845,185 +2845,625 @@
 ner)
-;
+ %7B
 %0A    
-%7D%0A%0A
     
-void unregister(final String playerName) %7B%0A        players.remove(playerName); // TODO synchronize deez!%0A        if (!inProgress) %7B%0A            broadcastPlayerLeft(
+Optional%3CString%3E optionalPlayerName = findPlayerName(listener);%0A        if (optionalPlayerName.isPresent()) %7B%0A            String playerName = optionalPlayerName.get();%0A            waitingRoomListeners.remove(playerName);%0A            broadcastPlayerLeft(playerName);%0A        %7D%0A    %7D%0A%0A    private Optional%3CString%3E findPlayerName(RummikubRoomListener listener) %7B%0A        return waitingRoomListeners.entrySet()%0A                .stream()%0A                .filter(entry -%3E entry.getValue() == listener)%0A                .map(Map.Entry::getKey)%0A                .findFirst();%0A    %7D%0A%0A    void unregister(final String 
 play
@@ -3461,33 +3461,34 @@
 ring playerName)
-;
+ %7B
 %0A        %7D%0A    %7D
@@ -3472,27 +3472,79 @@
 Name) %7B%0A        
-%7D
+players.remove(playerName); // TODO synchronize deez!
 %0A    %7D%0A%0A%7D%0A

change sequence of events