package io.github.privacystreams.utils;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Socket server
 */

public class PSDebugSocketServer {
    private static PSDebugSocketServer socketServer;

    public static PSDebugSocketServer v() {
        if (socketServer == null) {
            socketServer = new PSDebugSocketServer();
        }
        socketServer.checkConnection();
        return socketServer;
    }

    private void checkConnection() {
        if (serverSocket == null || serverSocket.isClosed()) {
            try {
                serverSocket = new ServerSocket(Globals.DebugConfig.socketPort);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (acceptingThread == null) {
            acceptingThread = new AcceptingThread();
        }

        if (!acceptingThread.accepting) {
            acceptingThread.start();
        }

    }

    public void disconnect() {
        acceptingThread.accepting = false;
        for (Socket socket : sockets) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        sockets.clear();
    }

    public void send(String... messages) {
        new SocketSendThread(messages).start();
    }

    private ServerSocket serverSocket;
    private AcceptingThread acceptingThread;
    private CopyOnWriteArraySet<Socket> sockets;

    private PSDebugSocketServer() {
        serverSocket = null;
        sockets = new CopyOnWriteArraySet<>();
        acceptingThread = new AcceptingThread();
    }

    private class AcceptingThread extends Thread {
        boolean accepting = false;

        @Override
        public void run() {
            try {
                accepting = true;
                Logging.debug("Start accepting socket clients...");
                while (accepting) {
                    Socket socket = serverSocket.accept();
                    Logging.debug("Accepted a socket client.");
                    sockets.add(socket);
                }
                Logging.debug("Stop accepting socket clients...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                disconnect();
            }
        }
    }

    private class SocketSendThread extends Thread {
        String[] messages;

        SocketSendThread(String... messages) {
            this.messages = messages;
        }

        public void run() {
            for (String message : this.messages) {
                byte[] bytes = message.getBytes();
                byte[] header = ByteBuffer.allocate(6).put((byte) 0xFF).put((byte) 0x00).putInt(bytes.length).array();

                Set<Socket> socketsToRemove = new HashSet<>();

                for (Socket socket : sockets) {
                    try {
                        socket.getOutputStream().write(header);
                        socket.getOutputStream().write(message.getBytes());
                        socket.getOutputStream().flush();
                    } catch (IOException e) {
                        socketsToRemove.add(socket);
                        e.printStackTrace();
                    }
                }

                for (Socket socket : socketsToRemove) {
                    try {
                        if (!socket.isClosed())
                            socket.close();
                        sockets.remove(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

@@ -3142,24 +3142,69 @@
 shSet%3C%3E();%0A%0A
+                synchronized (sockets) %7B%0A    
             
@@ -3251,38 +3251,46 @@
                 
+    
 try %7B%0A
+    
                 
@@ -3353,32 +3353,36 @@
                 
+    
 socket.getOutput
@@ -3394,32 +3394,81 @@
 m().
-write(message.getB
+flush();%0A                            socket.getOutputStream().write(b
 ytes
-()
 );%0A 
@@ -3482,32 +3482,36 @@
                 
+    
 socket.getOutput
@@ -3540,32 +3540,36 @@
                 
+   
+ 
 %7D catch (IOExcep
@@ -3570,32 +3570,36 @@
 OException e) %7B%0A
+    
                 
@@ -3651,32 +3651,36 @@
                 
+ 
+   
 e.printStackTrac
@@ -3696,34 +3696,42 @@
                 
-%7D%0A
+    %7D%0A    
                 
@@ -3741,32 +3741,36 @@
                 
+  
+  
 for (Socket sock
@@ -3805,32 +3805,36 @@
                 
+    
 try %7B%0A          
@@ -3843,24 +3843,28 @@
             
+ 
+   
 if (!socket.
@@ -3895,32 +3895,36 @@
                 
+   
+ 
 socket.close();%0A
@@ -3915,32 +3915,36 @@
 socket.close();%0A
+    
                 
@@ -3987,32 +3987,36 @@
                 
+    
 %7D catch (IOExcep
@@ -4017,32 +4017,36 @@
 OException e) %7B%0A
+    
                 
@@ -4066,32 +4066,58 @@
 ntStackTrace();%0A
+                        %7D%0A
                 

fix concurrency bug