import java.io.*;
import java.net.*;
import java.nio.*;
import java.util.*;
import java.util.logging.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Der Server muss auf einen anderen PC wie der Client laufen.<br>
 <br>
 * Verbindungsaufbau geschieht auf der sendenden und empfangenden Seite über
 * Port 42705 (TCP) <br>
 * Synchronisierungen werden von Port 42705 an Port 42705 per UDP gesendet.<br>
 * Ints und Doubles werden etwa 20x die Sekunde Synchronisiert (alle 50ms)<br>
 * Strings werden etwa 10x die Sekunde Synchronisiert (alle 100ms)<br>
 * Bei diesen Übertragungen werden vom Server mehr Daten gesendet, als ein normaler PC Bandbreite im Internet besitzt (Server-upload: ~2-10Mbit/s, DSL-16000 upload verfügbar: ~1Mbit/s)
 * Die Klasse ist daher nur für den Lanbetrieb am eigenen Router gedacht (upload
 * ~50Mbit/s).<br>
 * Server und Client müssen auf physikalisch anderen Systemen laufen (Bindung an den gleichen Port). Das kann in Schulnetzen mit virtualisierten Rechnern zu Problemen führen.
 * Integer und Double werden aktuell in Paketen zu je 100 verschickt (Integer:
 * 100*4 byte, Double: 100*8 byte)<br>
 <br>
 * Protokolle (Bytes : Inhalt)<br>
 <br>
 * Synchronisierung Integers/Doubles <br>
 * 0: clientNr (positiv), server (negativ bzw -1)  <br>
 * 1: 1/2 (1 -> Integer, 2 -> Double)<br>
 * 2-5: Position des ersten Eintrages im Array<br>
 * 6-9: Echte Anzahl der Daten (in Einheiten)<br>
 * Rest: Daten<br>
 <br>
 * Synchronisierung Strings <br>
 * 0: clientNr (positiv), server (negativ bzw. -1)<br>
 * 1: 3<br>
 * 2-5: Position im Array<br>
 * 6-9: Länge String (in bytes)<br>
 * Rest: Stringdaten<br>
 <br>
 * Protokoll zum Aufbau der Verbindung (Server -> Client):<br>
 <br>
 * 0: ClientNr<br>
 * 1: ClientSize<br>
 * 2-5: clientInts<br>
 * 6-9: clientDoubles<br>
 * 10-13: clientStrings<br>
 * 14-17: serverInts<br>
 * 18-21: serverDoubles<br>
 * 22-25: serverStrings<br>
 *<br>
 * Während dem Verbindungaufbau nimmt der Server jede Kontaktaufnahme auf dem lauschenden Port als Anfrage zum Beitritt wahr. Der Inhalt der Übertragung wird nicht geprüft. Die Verbindung erfolgt unverschlüsselt und ohne Identifizierung.
 @author André Greubeö
 */
public abstract class SpielDaten {

    protected DatagramSocket serverSocket;

    private ArrayList<String> clients;
    private ArrayList<Boolean> active;

    protected ArrayList<String[]> clientStrings;
    protected ArrayList<Double[]> clientDoubles;
    protected ArrayList<Integer[]> clientInts;

    protected String[] serverStrings;
    protected Double[] serverDoubles;
    protected Integer[] serverInts;

    protected int clientCount;
    protected int clientNr;

    int nrStringsPerClient;
    int nrDoublesPerClient;
    int nrIntsPerClient;
    int nrStringServer;
    int nrDoublesServer;
    int nrIntsServer;

    protected String serverIP;

    private SpielDaten(String server, int clientCount, int clientNr, int nrStringsPerClient, int nrDoublesPerClient, int nrIntsPerClient, int nrStringServer, int nrDoublesServer, int nrIntsServer) {

        serverIP = server;
        clients = null;
        this.clientCount = clientCount;
        this.clientNr = clientNr;

        this.nrStringsPerClient = nrStringsPerClient;
        this.nrDoublesPerClient = nrDoublesPerClient;
        this.nrIntsPerClient = nrIntsPerClient;

        this.nrStringServer = nrStringServer;
        this.nrIntsServer = nrIntsServer;
        this.nrDoublesServer = nrDoublesServer;

        initLists();

    }

    private SpielDaten(ArrayList<String> connectedIPs, int nrStringsPerClient, int nrDoublesPerClient, int nrIntsPerClient, int nrStringServer, int nrDoublesServer, int nrIntsServer) {
        clients = connectedIPs;
        clientCount = connectedIPs.size();
        serverIP = "127.0.0.1";

        this.nrStringsPerClient = nrStringsPerClient;
        this.nrDoublesPerClient = nrDoublesPerClient;
        this.nrIntsPerClient = nrIntsPerClient;

        this.nrStringServer = nrStringServer;
        this.nrIntsServer = nrIntsServer;
        this.nrDoublesServer = nrDoublesServer;

        initLists();

    }

    private synchronized String getServerIP() {
        return serverIP;
    }

    private void initLists() {
        clientStrings = new ArrayList<>();
        clientInts = new ArrayList<>();
        clientDoubles = new ArrayList<>();
        active = new ArrayList<>();
        for (int i = 0; i < clientCount; i++) {
            active.add(true);
            Integer[] curIntArr = new Integer[nrIntsPerClient];
            for (int j = 0; j < curIntArr.length; j++) {
                curIntArr[j0;
            }
            clientInts.add(curIntArr);

            Double[] curDoubleArr = new Double[nrDoublesPerClient];
            for (int j = 0; j < curDoubleArr.length; j++) {
                curDoubleArr[j0.0;
            }
            clientDoubles.add(curDoubleArr);

            String[] curStringArr = new String[nrStringsPerClient];
            for (int j = 0; j < curStringArr.length; j++) {
                curStringArr[j"";
            }
            clientStrings.add(curStringArr);
        }
        serverInts = new Integer[nrIntsServer];
        for (int i = 0; i < serverInts.length; i++) {
            serverInts[i0;
        }
        serverStrings = new String[nrStringServer];
        for (int i = 0; i < serverStrings.length; i++) {
            serverStrings[i"";
        }
        serverDoubles = new Double[nrDoublesServer];
        for (int i = 0; i < serverDoubles.length; i++) {
            serverDoubles[i0.0;
        }
        initRecievingSocket();

    }

    private boolean isActive(int clientNr) {
        return active.get(clientNr);
    }

    public synchronized String getClientString(int clientNr, int stringNr) {
        if (clientNr > clientCount) {
            throw new IllegalArgumentException("Spieler Nummer " + clientNr + " existiert nicht!");
        }
        if (stringNr > nrStringsPerClient) {
            throw new IllegalArgumentException("Jeder Spieler darf nur " + nrStringsPerClient + " Strings speichern, du versuchst auf das Element Nr. " + stringNr + " zuzugreifen (Zähler beginnt ab 0!)");
        }
        if (active.get(clientNr== false) {
            System.err.println("Daten von Spieler " + clientNr + " werden abgefragt, aber Spieler ist nicht mehr mit dem Spiel verbunden!");
        }
        return clientStrings.get(clientNr)[stringNr];
    }

    public synchronized double getClientDouble(int clientNr, int doubleNr) {
        if (clientNr > clientCount) {
            throw new IllegalArgumentException("Spieler Nummer " + clientNr + " existiert nicht!");
        }
        if (doubleNr > nrDoublesPerClient) {
            throw new IllegalArgumentException("Jeder Spieler darf nur " + nrStringsPerClient + " double-Werte speichern, du versuchst auf das Element Nr. " + doubleNr + " zuzugreifen (Zähler beginnt ab 0!)");
        }
        if (active.get(clientNr== false) {
            System.err.println("Daten von Spieler " + clientNr + " werden abgefragt, aber Spieler ist nicht mehr mit dem Spiel verbunden!");
        }
        return clientDoubles.get(clientNr)[doubleNr];
    }

    public synchronized int getClientInt(int clientNr, int intNr) {
        if (clientNr > clientCount) {
            throw new IllegalArgumentException("Spieler Nummer " + clientNr + " existiert nicht!");
        }
        if (intNr > nrIntsPerClient) {
            throw new IllegalArgumentException("Jeder Spieler darf nur " + nrStringsPerClient + " integer-Werte speichern, du versuchst auf das Element Nr. " + intNr + " zuzugreifen (Zähler beginnt ab 0!)");
        }
        if (active.get(clientNr== false) {
            System.err.println("Daten von Spieler " + clientNr + " werden abgefragt, aber Spieler ist nicht mehr mit dem Spiel verbunden!");
        }
        return clientInts.get(clientNr)[intNr];
    }

    public synchronized int getServerInt(int nr) {
        return serverInts[nr];
    }

    public synchronized double getServerDouble(int nr) {
        return serverDoubles[nr];
    }

    public synchronized String getServerString(int nr) {
        return serverStrings[nr];
    }

    public abstract int getMyId();

    protected abstract void updateData();

    protected abstract void setSeverString(int nr, String content);

    protected abstract void setServerInt(int nr, int content);

    protected abstract void setServerDouble(int nr, double content);

    abstract void setMyInt(int nr, int content);

    abstract void setMyDouble(int nr, double content);

    abstract void setMyString(int nr, String content);

    private void initRecievingSocket() {
        try {
            serverSocket = new DatagramSocket(42705);
        catch (SocketException ex) {
            Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
        }
        Thread recieving = new Thread() {
            public void run() {
                while (true) {
                    byte[] arr = new byte[2048];
                    DatagramPacket packet = new DatagramPacket(arr, arr.length);
                    try {
                        serverSocket.receive(packet);
                    catch (IOException ex) {
                        Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    handleData(packet.getData());
                }
            }
        };
        recieving.start();
    }

    private synchronized void handleData(byte[] handle) {

        if (getByteAsInt(handle, 0>= 0) {
            if (handle[1== 1) {
                handleIntegersClient(handle);
            else if (handle[1== 2) {
                handleDoublesClient(handle);
            else if (handle[1== 3) {
                handleStringsClient(handle);
            else {
                System.err.println("Data packet with unknown Context from client: " + handle[1]);
            }
        else if (handle[0== -1) {
            if (handle[1== 1) {
                handleIntegerServer(handle);
            else if (handle[1== 2) {
                handleDoubleServer(handle);
            else if (handle[1== 3) {
                handleStringServer(handle);
            else {
                System.err.println("Data packet with unknown Context from server: " + handle[1]);
            }
        }
    }

    private int getByteAsInt(byte[] arr, int pos) {
        return arr[pos? arr[pos(arr[pos256);
    }

    private void handleStringServer(byte[] data) {
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            in.readByte()//-1 wg. Server
            in.readByte()//3 wg. String
            int pos = in.readInt();
            int length = in.readInt();
            String content = new String(data, 10, length);
            if (pos >= serverStrings.length || pos < 0) {
                System.err.println("recieved server package with false pos: " + pos);
            }
            serverStrings[pos= content;
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleIntegerServer(byte[] data) {
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            in.readByte()//-1, da Server
            in.readByte()// 1, da Integer
            int posFirst = in.readInt();
            int dataLength = in.readInt();
            int maxPos = (posFirst + dataLength);
            if (maxPos > serverInts.length) {
                System.err.println("recieved invalid package wich wants to access a int beeing too big: " + maxPos);
            }
            for (int i = posFirst; i < maxPos; i++) {
                serverInts[i= in.readInt();
            }
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleDoubleServer(byte[] data) {
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            in.readByte()//-1, da Server
            in.readByte()// 2, da Double
            int posFirst = in.readInt();
            int dataLength = in.readInt();
            int maxPos = (posFirst + dataLength);
            if (maxPos > serverDoubles.length) {
                System.err.println("recieved invalid package wich wants to access a double beeing too big: " + maxPos);
            }
            for (int i = posFirst; i < maxPos; i++) {
                serverDoubles[i= in.readDouble();
            }
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void handleStringsClient(byte[] data) {
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            int clientNr = in.readByte();
            if (clientNr < || clientNr > clientStrings.size()) {
                System.err.println("recieved invalid package from a nonexisting client: " + clientNr);
            }
            in.readByte()// 3, da String
            int pos = in.readInt();
            int dataLength = in.readInt();
            String content = new String(data, 10, dataLength);
            if (pos > clientStrings.get(clientNr).length || pos < 0) {
                System.err.println("recieving invalid String on pos: " + pos);
            }
            clientStrings.get(clientNr)[pos= content;
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleIntegersClient(byte[] data) {

        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            int clientNr = in.readByte();
            if (clientNr < || clientNr > clientInts.size()) {
                System.err.println("recieved invalid package from a nonexisting client: " + clientNr);
            }
            in.readByte()// 1, da Integer
            int posFirst = in.readInt();
            int dataLength = in.readInt();
            int maxPos = (posFirst + dataLength);
            if (maxPos > clientInts.get(clientNr).length) {
                System.err.println("recieved invalid package wich wants to access a int beeing too big: " + maxPos);
            }
            for (int i = posFirst; i < maxPos; i++) {
                clientInts.get(clientNr)[i= in.readInt();
            }
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void handleDoublesClient(byte[] data) {
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            int clientNr = in.readByte();
            if (clientNr < || clientNr > clientDoubles.size()) {
                System.err.println("recieved invalid package from a nonexisting client: " + clientNr);
            }
            in.readByte()// 2, da Double
            int posFirst = in.readInt();
            int dataLength = in.readInt();
            int maxPos = (posFirst + dataLength);
            if (maxPos > clientDoubles.get(clientNr).length) {
                System.err.println("recieved invalid package wich wants to access a double beeing too big: " + maxPos);
            }
            for (int i = posFirst; i < maxPos; i++) {
                clientDoubles.get(clientNr)[i= in.readDouble();
            }
        catch (IOException ex) {
            Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private static byte[] intToByteArr(int value) {
        return new byte[]{
            (byte) (value >>> 24),
            (byte) (value >>> 16),
            (byte) (value >>> 8),
            (bytevalue};
    }

    private static byte[] toByteArray(double value) {
        byte[] bytes = new byte[8];
        ByteBuffer.wrap(bytes).putDouble(value);
        return bytes;
    }

    private static double toDouble(byte[] bytes) {
        return ByteBuffer.wrap(bytes).getDouble();
    }

    //VERBINDUNGSAUFBAU:
    private static ServerSocket awaitingSocket;
    private static ArrayList<String> awaitingClient;
    private static boolean serverStarted = false;
    private static JFrame awaitingFrame;

    private static int stServerInts, stServerDoubles, stServerStrings, stClientInts, stClientDoubles, stClientStrings;

    public static SpielDaten starteServer(int serverInts, int serverDoubles, int serverStrings, int clientInts, int clientDoubles, int clientStrings) {
        stServerInts = serverInts;
        stServerDoubles = serverDoubles;
        stServerStrings = serverStrings;
        stClientInts = clientInts;
        stClientDoubles = clientDoubles;
        stClientStrings = clientStrings;
        if (awaitingSocket != null) {
            throw new IllegalArgumentException("Server bereits am warten, schließe zunächst alle anderen Anwendungen!");
        }
        try {
            initServerLogik();

            ServerUpdater data = new ServerUpdater(awaitingClient, stClientStrings, stClientDoubles, stClientInts, stServerStrings, stServerDoubles, stServerInts);
            ServerDataObserver observer = new ServerDataObserver(data);
            return data;

        catch (Exception e) {
            System.err.println("Fehler beim aufsetzen des Servers: " + e.getMessage());
        }
        return null;
    }

    private static void initServerLogik() throws Exception {
        awaitingSocket = new ServerSocket(42705);
        awaitingClient = new ArrayList<String>();
        System.out.println("Server wartet mit IP " + InetAddress.getLocalHost() " auf Clients über TCP Port 42705");
        Thread awaiting = new Thread() {
            public void run() {
                while (!serverStarted) {
                    Socket in;
                    try {
                        in = awaitingSocket.accept();
                        String curAddress = in.getInetAddress().getHostAddress();
                        awaitingClient.add(curAddress);
                        System.out.println("client " + curAddress + " hat sich auf dem Server angemeldet!");
                    catch (IOException ex) {
                    }
                }
                System.out.println("Es sind keine Anmeldungen auf dem Server mehr möglich!");
            }
        };
        awaiting.start();
        initPanel();
        awaiting.join();
    }

    public static SpielDaten verbindeMitServer(String ip) {
        try {
            Socket s = new Socket();
            s.connect(new InetSocketAddress(ip, 42705)3000);

            ServerSocket serv = new ServerSocket(42705);
            OutputStream out = s.getOutputStream();
            out.write(0);
            out.flush();
            out.close();
            System.out.println("Warte darauf, dass der Server das Spiel beginnt");
            Socket inc = serv.accept();
            System.out.println("Server hat sich gemeldet!");
            DataInputStream in = new DataInputStream(inc.getInputStream());
            String servIp = inc.getInetAddress().getHostAddress();
            int myNr = in.readByte();
            int clientSize = in.readByte();
            stClientInts = in.readInt();
            stClientDoubles = in.readInt();
            stClientStrings = in.readInt();
            stServerInts = in.readInt();
            stServerDoubles = in.readInt();
            stServerStrings = in.readInt();
            in.close();
            serv.close();
            //  ClientUpdater up = ClientUpdater.factory(servIp, clientSize, clientSize, stServerInts, stServerInts, stServerInts, clientSize, clientSize, clientSize)
            ClientUpdater up = new SpielDaten.ClientUpdater(servIp, clientSize, myNr, stClientStrings, stClientDoubles, stClientInts, stServerStrings, stServerDoubles, stServerInts);
            return up;
        catch (Exception e) {
            System.err.println("Fehler beim Verbindungsaufbau: " + e.getMessage());
        }
        return null;
    }

    private static void initPanel() {
        awaitingFrame = new JFrame();
        awaitingFrame.setBounds(100100100100);
        JButton button = new JButton("Spiel beginnen");
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                try {
                    awaitingSocket.close();
                catch (IOException ex) {
                    Logger.getLogger(SpielDaten.class.getName()).log(Level.SEVERE, null, ex);
                }
                starteServer();
            }
        };
        button.addActionListener(listener);
        awaitingFrame.add(button);
        awaitingFrame.setVisible(true);
        awaitingFrame.pack();
    }

    private static void starteServer() {
        serverStarted = true;
        System.out.println("Server gestartet!");
        awaitingFrame.setVisible(false);
        try {
            byte curNr = 0;
            for (String cur : awaitingClient) {
                Socket s = new Socket(cur, 42705);
                OutputStream out = s.getOutputStream();
                out.write((bytecurNr);
                out.write((byteawaitingClient.size());
                out.write(intToByteArr(stClientInts));
                out.write(intToByteArr(stClientDoubles));
                out.write(intToByteArr(stClientStrings));
                out.write(intToByteArr(stServerInts));
                out.write(intToByteArr(stServerDoubles));
                out.write(intToByteArr(stServerStrings));
                out.flush();
                out.close();
                curNr++;
            }
        catch (Exception e) {
            System.err.println("Starten des Servers fehlgeschlagen: " + e.getMessage());
        }
    }

    private static class ClientUpdater extends SpielDaten {

        Integer[] myInts;
        Double[] myDoubles;
        String[] myStrings;

        InetAddress serverAdd;

        

        public ClientUpdater(String server, int clientCount, int clientNr, int nrStringsPerClient, int nrDoublesPerClient, int nrIntsPerClient, int nrStringServer, int nrDoublesServer, int nrIntsServer) {
            super(server, clientCount, clientNr, nrStringsPerClient, nrDoublesPerClient, nrIntsPerClient, nrStringServer, nrDoublesServer, nrIntsServer);
            myInts = new Integer[nrIntsPerClient];
            myDoubles = new Double[nrDoublesPerClient];
            myStrings = new String[nrStringsPerClient];
            for (int i = 0; i < myStrings.length; i++) {
                myStrings[i"";
            }
            for (int i = 0; i < myInts.length; i++) {
                myInts[i0;
            }
            for (int i = 0; i < myDoubles.length; i++) {
                myDoubles[i0.0;
            }
            try {
                serverAdd = InetAddress.getByName(server);
            catch (UnknownHostException ex) {
                Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
            }
            initSendingSocket();

        }

        private void initSendingSocket() {
            Thread sending = new Thread() {
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(1);
                        catch (InterruptedException ex) {
                            Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        try {
                            updateData();
                        catch (Exception ex) {
                            Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);

                        }
                    }
                }
            };
            sending.start();
        }

        private void sendInts() {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < myInts.length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byteclientNr);
                out.write((byte1);
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = myInts.length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(intToByteArr(myInts[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                sendToServer(out.toByteArray());
            }
        }

        private void sendDoubles() {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < myDoubles.length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byteclientNr);
                out.write((byte2);
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = myDoubles.length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(toByteArray(myDoubles[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                sendToServer(out.toByteArray());
            }
        }

        private void sendStrings() {
            for (int i = 0; i < myStrings.length; i++) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byteclientNr);
                out.write((byte3);
                byte[] arr = intToByteArr(i);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                byte[] contArr = myStrings[i].getBytes();
                byte[] lengthArr = intToByteArr(contArr.length);
                out.write(lengthArr[0]);
                out.write(lengthArr[1]);
                out.write(lengthArr[2]);
                out.write(lengthArr[3]);
                try {
                    out.write(contArr);
                catch (IOException ex) {
                    Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
                }
                sendToServer(out.toByteArray());
            }
        }

        private void sendToServer(byte[] arr) {
            DatagramPacket packet = new DatagramPacket(arr, arr.length, serverAdd, 42705);
            try {
                serverSocket.send(packet);
            catch (IOException ex) {
                Logger.getLogger(ClientUpdater.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        private int updateCycle = 0;

        @Override
        protected synchronized void updateData() {
            updateCycle++;

            boolean update = updateCycle % 1000 == 0;
            if (doubleChanged || update) {
                doubleChanged = false;
                sendDoubles();
            }
            if (intChanged || update) {
                intChanged = false;
                sendInts();
            }
            if (stringChanged || update) {
                sendStrings();
            }
        }

        @Override
        protected void setSeverString(int nr, String content) {
            throw new UnsupportedOperationException("setServerString kann nicht von einem Client aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        @Override
        protected void setServerInt(int nr, int content) {
            throw new UnsupportedOperationException("setServerInt kann nicht von einem Client aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        @Override
        protected void setServerDouble(int nr, double content) {
            throw new UnsupportedOperationException("serServerDouble kann nicht von einem Client aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        private boolean intChanged = false;
        private boolean doubleChanged = false;
        private boolean stringChanged = false;

        @Override
        public synchronized void setMyInt(int nr, int content) {
            myInts[nr= content;
            intChanged = true;
        }

        @Override
        public synchronized void setMyDouble(int nr, double content) {
            myDoubles[nr= content;
            doubleChanged = true;
        }

        @Override
        public synchronized void setMyString(int nr, String content) {
            myStrings[nr= content;
            stringChanged = true;
        }

        @Override
        public int getMyId() {
            return clientNr;
        }

        @Override
        public synchronized int getClientInt(int clientNr, int intNr) {
            if (clientNr == getMyId()) {
                return myInts[intNr];
            else {
                return super.getClientInt(clientNr, intNr);
            }
        }

        @Override
        public synchronized double getClientDouble(int clientNr, int doubleNr) {
            if (clientNr == getMyId()) {
                return myDoubles[doubleNr];
            else {
                return super.getClientDouble(clientNr, doubleNr);
            }
        }

        @Override
        public synchronized String getClientString(int clientNr, int stringNr) {
            if (clientNr == getMyId()) {
                return myStrings[stringNr];
            else {
                return super.getClientString(clientNr, stringNr);
            }
        }
    }

    private static class ServerUpdater extends SpielDaten {

        ArrayList<InetAddress> clients = new ArrayList<>();

        public ServerUpdater(ArrayList<String> connectedIPs, int nrStringsPerClient, int nrDoublesPerClient, int nrIntsPerClient, int nrStringServer, int nrDoublesServer, int nrIntsServer) {
            super(connectedIPs, nrStringsPerClient, nrDoublesPerClient, nrIntsPerClient, nrStringServer, nrDoublesServer, nrIntsServer);
            for (String cur : connectedIPs) {
                try {
                    clients.add(InetAddress.getByName(cur));
                catch (UnknownHostException ex) {
                    Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            initSendingSocket();
        }

        private int updateCycle = 0;

        private void initSendingSocket() {
            Thread sending = new Thread() {
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(10);
                        catch (InterruptedException ex) {
                            Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        try {
                            updateData();
                        catch (Exception ex) {
                            Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            };
            sending.start();
        }

        @Override
        protected void updateData() {
            //Rufe update mindestens alle 5ms auf!
            if (updateCycle % == 0) { //Rest bei 10: 0, 5
                broadCastClientInts();
            else if (updateCycle % == 1) {//Rest bei 10: 1, 6
                broadCastClientDoubles();
            else if (updateCycle % 10 == 2) {//Rest bei 10: 2
                broadCastClientStrings();
            else if (updateCycle % 10 == 7) { //Rest bei 10: 7
                broadCastServerStrings();
            else if (updateCycle % == 3) { // Test bei 10: 3, 8
                broadCastServerInts();
            else if (updateCycle % == 4) {// Rest bei 10: 4, 9
                broadCastServerDoubles();
            }

        }

        private void broadCastServerStrings() {
            for (int i = 0; i < serverStrings.length; i++) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byte-1)//Server
                out.write((byte3)//String
                byte[] arr = intToByteArr(i);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                byte[] contArr = serverStrings[i].getBytes();
                byte[] lengthArr = intToByteArr(contArr.length);
                out.write(lengthArr[0]);
                out.write(lengthArr[1]);
                out.write(lengthArr[2]);
                out.write(lengthArr[3]);
                try {
                    out.write(contArr);
                catch (IOException ex) {
                    Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                }
                broadCastData(out.toByteArray());
            }
        }

        private void broadCastServerInts() {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < serverInts.length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byte-1)// Server
                out.write((byte1)//Integer
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = serverInts.length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(intToByteArr(serverInts[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                broadCastData(out.toByteArray());
            }
        }

        private void broadCastServerDoubles() {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < serverDoubles.length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((byte-1)//Server
                out.write((byte2)//Doubles
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = serverDoubles.length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(toByteArray(serverDoubles[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                broadCastData(out.toByteArray());
            }
        }

        private void broadCastClientInts() {
            for (int i = 0; i < clients.size(); i++) {
                broadcastClientInt(i);
            }
        }

        private void broadCastClientDoubles() {
            for (int i = 0; i < clients.size(); i++) {
                broadcastClientDouble(i);
            }
        }

        private void broadCastClientStrings() {
            for (int i = 0; i < clients.size(); i++) {
                broadcastClientString(i);
            }
        }

        private void broadcastClientString(int curClientNr) {
            for (int i = 0; i < clientStrings.get(curClientNr).length; i++) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((bytecurClientNr);
                out.write((byte3)//String
                byte[] arr = intToByteArr(i);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                byte[] contArr = clientStrings.get(curClientNr)[i].getBytes();
                byte[] lengthArr = intToByteArr(contArr.length);
                out.write(lengthArr[0]);
                out.write(lengthArr[1]);
                out.write(lengthArr[2]);
                out.write(lengthArr[3]);
                try {
                    out.write(contArr);
                catch (IOException ex) {
                    Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                }
                broadCastData(out.toByteArray());
            }
        }

        private void broadcastClientInt(int curClientNr) {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < clientInts.get(curClientNr).length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((bytecurClientNr);
                out.write((byte1)//Integer
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = clientInts.get(curClientNr).length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(intToByteArr(clientInts.get(curClientNr)[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                broadCastData(out.toByteArray());
            }
        }

        private void broadcastClientDouble(int curClientNr) {
            int firstPos = 0;
            int incSize = 100;
            while (firstPos < clientDoubles.get(curClientNr).length) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                out.write((bytecurClientNr);
                out.write((byte2)//Doubles
                byte[] arr = intToByteArr(firstPos);
                out.write(arr[0]);
                out.write(arr[1]);
                out.write(arr[2]);
                out.write(arr[3]);
                int dataSize = clientDoubles.get(curClientNr).length - firstPos;
                if (dataSize > incSize) {
                    dataSize = incSize;
                }
                byte[] lengthData = intToByteArr(dataSize);
                out.write(lengthData[0]);
                out.write(lengthData[1]);
                out.write(lengthData[2]);
                out.write(lengthData[3]);
                for (int j = firstPos; j < (firstPos + dataSize); j++) {
                    try {
                        out.write(toByteArray(clientDoubles.get(curClientNr)[j]));
                    catch (IOException ex) {
                        Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                firstPos += incSize;
                broadCastData(out.toByteArray());
            }
        }

        private void broadCastData(byte[] arr) {
            for (InetAddress cur : clients) {
                DatagramPacket curPacket = new DatagramPacket(arr, arr.length, cur, 42705);
                try {
                    serverSocket.send(curPacket);
                catch (IOException ex) {
                    Logger.getLogger(ServerUpdater.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        @Override
        protected void setSeverString(int nr, String content) {
            if (nr < || nr >= serverStrings.length) {
                throw new IllegalArgumentException("Du versuchst Server String nr " + nr + " zu ändern, aber es gibt nur " + serverStrings.length + " Strings (zählend ab 0)!");
            }
            serverStrings[nr= content;
        }

        @Override
        protected void setServerInt(int nr, int content) {
            if (nr < || nr >= serverInts.length) {
                throw new IllegalArgumentException("Du versuchst Server Int nr " + nr + " zu ändern, aber es gibt nur " + serverInts.length + " Ints (zählend ab 0)!");
            }
            serverInts[nr= content;
        }

        @Override
        protected void setServerDouble(int nr, double content) {
            if (nr < || nr >= serverDoubles.length) {
                throw new IllegalArgumentException("Du versuchst Server Double nr " + nr + " zu ändern, aber es gibt nur " + serverDoubles.length + " Doubles (zählend ab 0)!");
            }
            serverDoubles[nr= content;
        }

        @Override
        protected void setMyInt(int nr, int content) {
            throw new UnsupportedOperationException("setMyInt sollte nicht von einem Server aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        @Override
        void setMyDouble(int nr, double content) {
            throw new UnsupportedOperationException("setMyDouble sollte nicht von einem Server aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        @Override
        void setMyString(int nr, String content) {
            throw new UnsupportedOperationException("setMySring sollte nicht von einem Server aufgerufen werden!")//To change body of generated methods, choose Tools | Templates.
        }

        @Override
        public int getMyId() {
            throw new UnsupportedOperationException("Der Server hat keine ID!")//To change body of generated methods, choose Tools | Templates.
        }

    }

    private static class ServerDataObserver {

        SpielDaten data;

        private JFrame frame;

        public ServerDataObserver(SpielDaten daten) {
            this.data = daten;
            initFrame();
        }

        private void initFrame() {

            frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            frame.setPreferredSize(new Dimension(1200800));
            frame.setBounds(1001001200800);
            JPanel mainPane = new JPanel();

            GridLayout grid = new GridLayout((data.clientCount + 1)1);
            mainPane.setLayout(grid);

            mainPane.setPreferredSize(new Dimension(1200800));
            frame.add(mainPane);
            mainPane.add(initServerPanel());
            for (int i = 0; i < data.clientCount; i++) {
                JPanel cur = initClientPanel(i);
                mainPane.add(cur);
            }

            frame.setVisible(true);
            frame.pack();

        }

        private JPanel initServerPanel() {

            JTable tableInt = new JTable(1, data.nrIntsServer);
            JTable tableDouble = new JTable(1, data.nrDoublesServer);
            JTable tableString = new JTable(1, data.nrStringServer);

            Thread updater = new Thread() {
                public void run() {
                    while (true) {
                        for (int i = 0; i < data.nrIntsServer; i++) {
                            tableInt.setValueAt(data.getServerInt(i)0, i);
                        }
                        for (int i = 0; i < data.nrDoublesServer; i++) {
                            tableDouble.setValueAt(data.getServerDouble(i)0, i);
                        }
                        for (int i = 0; i < data.nrStringServer; i++) {
                            tableString.setValueAt(data.getServerString(i)0, i);
                        }
                        try {
                            Thread.sleep(30);
                        catch (InterruptedException ex) {
                            Logger.getLogger(ServerDataObserver.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            };

            JScrollPane paneInt = new JScrollPane(tableInt);
            JScrollPane paneDouble = new JScrollPane(tableDouble);
            JScrollPane paneString = new JScrollPane(tableString);

            JPanel panel = new JPanel();
            GridLayout grid = new GridLayout(71);
            panel.setLayout(grid);
            panel.setPreferredSize(new Dimension(600100));

            panel.add(new JLabel("Server data:"));
            panel.add(new JLabel("Integer Values:"));
            panel.add(paneInt);
            panel.add(new JLabel("Double Values:"));
            panel.add(paneDouble);
            panel.add(new JLabel("String Values:"));
            panel.add(paneString);

            updater.start();
            return panel;

        }

        private JPanel initClientPanel(int nr) {

            JTable tableInt = new JTable(1, data.nrIntsPerClient);
            JTable tableDouble = new JTable(1, data.nrDoublesPerClient);
            JTable tableString = new JTable(1, data.nrStringsPerClient);

            Thread updater = new Thread() {
                public void run() {
                    while (true) {
                        for (int i = 0; i < data.nrIntsPerClient; i++) {
                            tableInt.setValueAt(data.getClientInt(nr, i)0, i);
                        }
                        for (int i = 0; i < data.nrDoublesPerClient; i++) {
                            tableDouble.setValueAt(data.getClientDouble(nr, i)0, i);
                        }
                        for (int i = 0; i < data.nrStringsPerClient; i++) {
                            tableString.setValueAt(data.getClientString(nr, i)0, i);
                        }
                        try {
                            Thread.sleep(30);
                        catch (InterruptedException ex) {
                            Logger.getLogger(ServerDataObserver.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            };

            JScrollPane paneInt = new JScrollPane(tableInt);
            JScrollPane paneDouble = new JScrollPane(tableDouble);
            JScrollPane paneString = new JScrollPane(tableString);

            JPanel panel = new JPanel();
            GridLayout grid = new GridLayout(71);
            panel.setLayout(grid);
            panel.setPreferredSize(new Dimension(600100));

            panel.add(new JLabel("Client nr " + nr + " (" + data.clients.get(nr")"));
            panel.add(new JLabel("Integer Values:"));
            panel.add(paneInt);
            panel.add(new JLabel("Double Values:"));
            panel.add(paneDouble);
            panel.add(new JLabel("String Values:"));
            panel.add(paneString);

            updater.start();
            return panel;
        }

    }

}