///*
// * The MIT License (MIT)
// *
// * FXGL - JavaFX Game Library
// *
// * Copyright (c) 2015-2017 AlmasB (almaslvl@gmail.com)
// *
// * Permission is hereby granted, free of charge, to any person obtaining a copy
// * of this software and associated documentation files (the "Software"), to deal
// * in the Software without restriction, including without limitation the rights
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// * copies of the Software, and to permit persons to whom the Software is
// * furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included in
// * all copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// * SOFTWARE.
// */
//
//package com.almasb.fxgl.net;
//
//import org.apache.logging.log4j.LogManager;
//import org.apache.logging.log4j.Logger;
//
//import java.io.*;
//import java.net.*;
//import java.util.ArrayList;
//import java.util.Collections;
//import java.util.List;
//
///**
// * MultiServer for multiple concurrent network connections (clients)
// * <p>
// * Since there isn't a 1 to 1 connection, {@link #stop()} must be explicitly
// * called to attempt a clean shutdown of the server
// *
// * @author Almas Baimagambetov (AlmasB) (almaslvl@gmail.com)
// */
//public final class MultiServer extends NetworkConnection {
//
// private static final Logger log = LogManager.getLogger(MultiServer.class);
//
// private TCPConnectionThread tcpThread = new TCPConnectionThread();
// private UDPConnectionThread udpThread = new UDPConnectionThread();
//
// private final List<TCPThread> tcpThreads = Collections.synchronizedList(new ArrayList<>());
//
// private final List<FullInetAddress> addresses = Collections.synchronizedList(new ArrayList<>());
// private int tcpPort, udpPort;
//
// /**
// * Constructs and configures a multi server with default ports
// * No network operation is done at this point.
// */
// public MultiServer() {
// this(NetworkConfig.DEFAULT_TCP_PORT, NetworkConfig.DEFAULT_UDP_PORT);
// }
//
// /**
// * Constructs and configures a multi server with specified ports
// * No network operation is done at this point.
// *
// * @param tcpPort tcp port to use
// * @param udpPort udp port to use
// */
// public MultiServer(int tcpPort, int udpPort) {
// this.tcpPort = tcpPort;
// this.udpPort = udpPort;
//
// tcpThread.setDaemon(true);
// udpThread.setDaemon(true);
// }
//
// /**
// * Starts the server. This performs an actual network operation
// * of binding to ports and listening for incoming connections.
// */
// public void start() {
// tcpThread.start();
// udpThread.start();
// }
//
// /**
// * Sends a message to all connected clients that
// * the server is about to shut down. Then stops the server
// * and the connection threads.
// * <p>
// * Further calls to {@link #send(Serializable)} will
// * throw IllegalStateException
// */
// public void stop() {
// sendClosingMessage();
//
// tcpThread.running = false;
// try {
// tcpThread.server.close();
// } catch (IOException ignored) {
// }
//
// tcpThreads.forEach(t -> t.running = false);
// udpThread.running = false;
// }
//
// @Override
// public void close() {
// stop();
// }
//
// @Override
// protected void sendUDP(Serializable data) throws Exception {
// if (udpThread.running) {
// byte[] buf = toByteArray(data);
// synchronized (addresses) {
// for (FullInetAddress addr : addresses) {
// try {
// udpThread.outSocket.send(new DatagramPacket(buf, buf.length, addr.address, addr.port));
// } catch (Exception e) {
// log.warn("Failed to send UDP message: " + e.getMessage());
// }
// }
// }
// } else {
// throw new IllegalStateException("UDP connection not active");
// }
// }
//
// @Override
// protected void sendTCP(Serializable data) throws Exception {
// synchronized (tcpThreads) {
// tcpThreads.stream().filter(tcpThread -> tcpThread.running).forEach(tcpThread -> {
// try {
// tcpThread.outputStream.writeObject(data);
// } catch (Exception e) {
// log.warn("Failed to send TCP message: " + e.getMessage());
// }
// });
// }
// }
//
// private class TCPConnectionThread extends Thread {
// private boolean running = true;
// private ServerSocket server;
//
// @Override
// public void run() {
// try {
// server = new ServerSocket(tcpPort);
// } catch (Exception e) {
// log.warn("Exception during TCP connection creation: " + e.getMessage());
// running = false;
// return;
// }
//
// while (running) {
// try {
// Socket socket = server.accept();
// socket.setTcpNoDelay(true);
//
// TCPThread t = new TCPThread(socket);
// t.setDaemon(true);
// tcpThreads.add(t);
// t.start();
// } catch (Exception e) {
// log.warn("Exception during TCP connection execution: " + e.getMessage());
// }
// }
//
// try {
// server.close();
// } catch (Exception ignored) {
// }
// log.debug("TCP connection closed normally");
// }
// }
//
// private class TCPThread extends Thread {
// private boolean running = false;
// private ObjectOutputStream outputStream;
// private Socket socket;
//
// public TCPThread(Socket socket) {
// this.socket = socket;
// }
//
// @Override
// public void run() {
// try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
// ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
// outputStream = out;
// socket.setTcpNoDelay(true);
// running = true;
//
// while (running) {
// Object data = in.readObject();
// if (data == ConnectionMessage.CLOSE) {
// running = false;
// break;
// }
// if (data == ConnectionMessage.CLOSING) {
// outputStream.writeObject(ConnectionMessage.CLOSE);
// running = false;
// break;
// }
//
// parsers.getOrDefault(data.getClass(), d -> {
// }).parse((Serializable) data);
// }
// } catch (Exception e) {
// log.warn("Exception during TCP connection execution: " + e.getMessage());
// running = false;
// tcpThreads.remove(this);
// try {
// socket.close();
// } catch (IOException ignored) {
// }
// return;
// }
//
// tcpThreads.remove(this);
// try {
// socket.close();
// } catch (IOException ignored) {
// }
// log.debug("TCP connection closed normally");
// }
// }
//
// private class UDPConnectionThread extends Thread {
// private DatagramSocket outSocket;
// private boolean running = false;
//
// @Override
// public void run() {
// try (DatagramSocket socket = new DatagramSocket(udpPort)) {
// outSocket = socket;
// running = true;
//
// while (running) {
// try {
// byte[] buf = new byte[16384];
// DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
// socket.receive(datagramPacket);
//
// try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()))) {
// Object data = in.readObject();
// FullInetAddress addr = new FullInetAddress(datagramPacket.getAddress(), datagramPacket.getPort());
//
// if (data == ConnectionMessage.OPEN) {
// if (!addresses.contains(addr)) {
// addresses.add(addr);
// }
// }
// if (data == ConnectionMessage.CLOSE) {
// addresses.remove(addr);
// continue;
// }
// if (data == ConnectionMessage.CLOSING) {
// byte[] sendBuf = toByteArray(ConnectionMessage.CLOSE);
// udpThread.outSocket.send(new DatagramPacket(sendBuf, sendBuf.length, addr.address, addr.port));
// continue;
// }
//
// parsers.getOrDefault(data.getClass(), d -> {
// }).parse((Serializable) data);
// }
// } catch (Exception e) {
// log.warn("Exception during UDP connection execution: " + e.getMessage());
// }
// }
// } catch (Exception e) {
// log.warn("Exception during UDP connection execution: " + e.getMessage());
// running = false;
// return;
// }
//
// log.debug("UDP connection closed normally");
// }
// }
//
// private static class FullInetAddress {
// private InetAddress address;
// private int port;
//
// public FullInetAddress(InetAddress address, int port) {
// this.address = address;
// this.port = port;
// }
//
// @Override
// public boolean equals(Object obj) {
// if (obj instanceof FullInetAddress) {
// FullInetAddress other = (FullInetAddress) obj;
// return this.address.getHostAddress().equals(
// other.address.getHostAddress())
// && this.port == other.port;
// }
// return false;
// }
// }
//}