package common;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.logging.Logger;

import communication.MessageUtil;
import communication.Port;
import communication.messages.Message;
import error.ExceptionUtil;

/**
 * Base class for central implementation of receiving packets
 */
public abstract class AbstractReceiver {
	private final DatagramSocket socket;

	public AbstractReceiver(Port port) throws IOException {
		socket = new DatagramSocket(port.getPortNumber());
	}

	/**
	 * Constantly receives packets from the port specified in the constructor.
	 * Converts received packets to `Message` objects and calls `handleMessage`
	 * which is implemented by the subclass receivers.
	 *
	 * @param logger the logger of the receiver
	 * @throws IOException
	 */
	public void receiveAndHandlePackets(Logger logger) throws IOException {
		logger.info("Now Listening for packets...");
		while (true) {
			byte[] data = new byte[MessageUtil.BUFFER_SIZE];
			DatagramPacket packet = new DatagramPacket(data, data.length);

			// wait to receive a packet
			socket.receive(packet);

			new Thread(() -> {
				Message msg = convertMessage(packet.getData(), logger);
				if (msg != null) {
					logger.fine("Received message: " + msg);
					try {
						handleMessage(msg);
					} catch (IOException e) {
						ExceptionUtil.handleRecoverableException(e, "Error handling message.", logger);
					}
				}
			}).start();
		}
	}

	private Message convertMessage(byte[] data, Logger logger) {
		try {
			// convert the received packet into a message
			return MessageUtil.byteArrayToMessage(data);
		} catch (ClassNotFoundException | IOException e) {
			ExceptionUtil.handleRecoverableException(e,
					"Received a message not meant for this subsystem", logger);
		}
		return null;
	}

	/**
	 * Handles a given message for a subsystem receiver.
	 *
	 * @param message message to process
	 * @throws IOException if there's an error sending the message
	 */
	public abstract void handleMessage(Message message) throws IOException;
}
