import java.net.*;
import java.io.*;
import java.util.*;
import java.nio.charset.StandardCharsets;

class HostAddr {
	public HostAddr(String ip, int port){
		Ip = ip;
		Port = port;
	}
    public String Ip;
    public int Port;
}

class InvalidResponseException extends Exception
{
	//Parameterless Constructor
	public InvalidResponseException() {}

	//Constructor that accepts a message
	public InvalidResponseException(String message)
	{
	super(message);
	}
}

public class ListRen {
	public static final String RENLIST_SERVER = "master-gsa.renlist.n00b.hk";
	public static final int RENLIST_PORT = 28900;

	private static HashMap<String, String> GetHostStatus(HostAddr host) throws IOException {
		HashMap<String, String> gameOptions = new HashMap<String, String>();
		DatagramSocket sock = new DatagramSocket();

		// Prepare and send the \status\ UDP datagram
		byte[] statusBytes = "\\status\\".getBytes();
		DatagramPacket packet = new DatagramPacket(statusBytes, statusBytes.length, InetAddress.getByName(host.Ip), host.Port);
		sock.send(packet);

		// Create and read into big buffer for receiving
		packet.setData(new byte[2048]);
		sock.setSoTimeout(2000);
		sock.receive(packet);

		// Convert the data into a string
		// (And strip off the first backslash in the process)
		String respStr = new String(Arrays.copyOfRange(packet.getData(), 0, packet.getLength()), StandardCharsets.UTF_8);

		// Split by single backslash
		// escaped once for java and once for regex
		// resulting in "\\\\"
		String[] fields = respStr.split("\\\\");


		for(int i = 0; i < fields.length;){
			if(fields[i].equals("final")){
				// Final, exit here.
				break;
			}else if(fields[i].equals("")){
				// Empty, skip.
				i++;
			} else {
				// Valid, add to map.
				gameOptions.put(fields[i], fields[i+1]);
				i+=2;

			}
		}

		sock.close();

		return gameOptions;
	}

	private static List<HostAddr> GetHostAddrs() throws UnknownHostException, IOException, InvalidResponseException {
		// Connect to GSA master server
		Socket sock = null;
		try {
			sock = new Socket(RENLIST_SERVER, RENLIST_PORT);
		} catch (UnknownHostException e) {
			System.err.println("Unknown host: " + RENLIST_SERVER);
			throw e;
		} catch (IOException e) {
			System.err.println("IOException on creating socket to: " + RENLIST_SERVER);
			throw e;
		}

		// Try to get the host list from the GSA master server.
		List<HostAddr> hostList = new ArrayList<HostAddr>();
		try {
			InputStream sin = sock.getInputStream();
			OutputStream sout = sock.getOutputStream();

			// Allocate and read in buffer for the intial "\basic\\secure\IGNORE" query
			// Sent by the n00b.hk GSA master server.
			byte[] respData = new byte[21];
			sin.read(respData);
			String query = new String(respData, StandardCharsets.UTF_8);

			if(query.equals("\\basic\\\\secure\\IGNORE")){
				System.out.println("Got basic query.");

				// Send query to get servers.
				sout.write("\\gamename\\ccrenegade\\enctype\\0\\validate\\TVJFLK6J\\final\\\\queryid\\1.1\\list\\cmp\\gamename\\ccrenegade\\where\\\\final\\".getBytes());

				// Read in each host
				while(true){
					// Read in binary host data (int32 ip, int16 port)
					byte[] hostData = new byte[6];
					sin.read(hostData);
					String hostDataAsString = new String(hostData, StandardCharsets.UTF_8);

					// Check if it is the end of the server/host list,
					// -- HACK
					if(hostDataAsString.equals("\\final")){
						//System.out.println("Got final query. Done reading server list.");
						break;
					}

					// Parse bytes to ip and port,
					// I don't program in Java, this may be a horrible way to do this.
					// But it works :)
					String ip = String.format("%d.%d.%d.%d", hostData[0]&0xff, hostData[1]&0xff, hostData[2]&0xff, hostData[3]&0xff);
					int port = ((hostData[5]&0xff)|(short) ((hostData[4]&0xff)<<8))&0xffff;

					hostList.add(new HostAddr(ip, port));
					//System.out.printf("%s:%d\n", ip, port);
				}
			} else {
				throw new InvalidResponseException(String.format("Server returned something that wasn't the basic query: %s\n", query));
			}

			sin.close();
			sock.close();
    	
    	} catch (IOException e) {
			throw e;
		}
		return hostList;
	}


	public static void main(String[] args) {
		try{
			List<HostAddr> hosts = GetHostAddrs();

			for(HostAddr h : hosts){
				HashMap<String, String> options;
				try{
					options = GetHostStatus(h);
				} catch(SocketTimeoutException e){
					System.out.printf("GSA Serv: %s:%d timed out.\n", h.Ip, h.Port);
					continue;
				}
				System.out.printf("%s:%s | %s | %s | %s/%s\n", h.Ip, options.get("hostport"), options.get("hostname"), options.get("mapname"), options.get("numplayers"), options.get("maxplayers"));


			}
		} catch(Exception e){
			System.out.println(e);
		}
	}
}