package server;

import java.io.*;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.*;

abstract class Server {

    final static private String dateFormat = "[HH:mm:ss]";

    protected int port;

    private BufferedWriter historyBufferedWriter;
    private LinkedList<String> messages = new LinkedList<>();
    private HashMap<String, Session> sessions = new HashMap<>();

    private SecureRandom random = new SecureRandom();
    private SimpleDateFormat dateFormatter = new SimpleDateFormat(dateFormat);

    public Server(int serverPort, String historyFilename) throws IOException {
        super();
        port = serverPort;
        File historyFile = new File(historyFilename);
        loadMessageHistory(historyFile);
        FileWriter historyFileWriter = new FileWriter(historyFile, true);
        historyBufferedWriter = new BufferedWriter(historyFileWriter);
    }

    public abstract void run() throws Exception;

    protected void addMessage(String sessionId, String message) throws IOException {
        if (!checkSession(sessionId))
            throw new RuntimeException("Invalid session ID");
        Date now = new Date();
        String username = sessions.get(sessionId).getUsername();
        String line =
                dateFormatter.format(now) + " <" + username + "> " + message;
        messages.add(line);
        try {
            historyBufferedWriter.write(line);
            historyBufferedWriter.newLine();
            historyBufferedWriter.flush();
        } catch (IOException e) {
            System.err.println(
                    "ERROR: Failed to write to history file (" + e.getMessage()
                    + ")"
            );
        }
        broadcastMessage(line);
    }

    protected String addUser(Session session) {
        if (!checkUsername(session.getUsername()))
            throw new RuntimeException("Username already in use");
        String sessionId = registerSession(session);
        session.sendMessages(messages);
        broadcastUserList();
        return sessionId;
    }

    protected void removeUser(String sessionId) {
        if (!checkSession(sessionId))
            throw new RuntimeException("Invalid session ID");
        sessions.remove(sessionId);
        broadcastUserList();
    }

    protected void renameUser(String sessionId, String newUsername) {
        if (!checkSession(sessionId))
            throw new RuntimeException("Invalid session ID");
        if (!checkUsername(newUsername))
            throw new RuntimeException("Username already in use");
        sessions.get(sessionId).changeUsername(newUsername);
    }

    private void broadcastMessage(String message) {
        for (Session session : sessions.values())
            session.sendMessage(message);
    }

    private void broadcastUserList() {
        String[] userList = getUserList();
        for (Session session : sessions.values())
            session.sendUserList(userList);
    }

    private boolean checkSession(String sessionId) {
        return sessions.containsKey(sessionId);
    }

    private boolean checkUsername(String username) {
        for (Session value : sessions.values())
            if ((value).getUsername().equals(username))
                return false;
        return true;
    }

    private String getNextSessionId() {
        return new BigInteger(130, random).toString(32);
    }

    private String[] getUserList() {
        String[] users = new String[sessions.size()];
        int i = 0;
        for (Session session : sessions.values()) {
            users[i] = session.getUsername();
            i++;
        }
        return users;
    }

    private void loadMessageHistory(File historyFile) throws IOException {
        if (historyFile.createNewFile())
            return;
        FileReader reader = new FileReader(historyFile);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line;
        while((line = bufferedReader.readLine()) != null) {
            if (!line.isEmpty())
                messages.add(line);
        }
        bufferedReader.close();
    }

    private String registerSession(Session session) {
        String sessionId = getNextSessionId();
        sessions.put(sessionId, session);
        return sessionId;
    }
}

@@ -1375,382 +1375,8 @@
 ge;%0A
-        messages.add(line);%0A        try %7B%0A            historyBufferedWriter.write(line);%0A            historyBufferedWriter.newLine();%0A            historyBufferedWriter.flush();%0A        %7D catch (IOException e) %7B%0A            System.err.println(%0A                    %22ERROR: Failed to write to history file (%22 + e.getMessage()%0A                    + %22)%22%0A            );%0A        %7D%0A
     
@@ -1690,32 +1690,106 @@
 castUserList();%0A
+        broadcastMessage(%22* %22 + session.getUsername() + %22 has joined *%22);%0A
         return s
@@ -1947,32 +1947,97 @@
 d session ID%22);%0A
+        String username = sessions.get(sessionId).getUsername();%0A
         sessions
@@ -2081,24 +2081,83 @@
 UserList();%0A
+        broadcastMessage(%22* %22 + username + %22 has left *%22);%0A
     %7D%0A%0A    p
@@ -2428,32 +2428,50 @@
 n use%22);%0A       
+ Session session =
  sessions.get(se
@@ -2482,96 +2482,697 @@
 nId)
-.changeUsername(newUsername);%0A    %7D%0A%0A    private void broadcastMessage(String message) %7B
+;%0A        String oldUsername = session.getUsername();%0A        session.changeUsername(newUsername);%0A        broadcastUserList();%0A        broadcastMessage(%0A                %22* %22 + oldUsername + %22 is now known as %22 + newUsername + %22 *%22%0A        );%0A    %7D%0A%0A    private void broadcastMessage(String message) %7B%0A        messages.add(message);%0A        try %7B%0A            historyBufferedWriter.write(message);%0A            historyBufferedWriter.newLine();%0A            historyBufferedWriter.flush();%0A        %7D catch (IOException e) %7B%0A            System.err.println(%0A                    %22ERROR: Failed to write to history file (%22 + e.getMessage()%0A                            + %22)%22%0A            );%0A        %7D
 %0A   

Add join/rename/part messages