package org.reprap.comms;

import java.io.IOException;

import org.reprap.Device;
import org.reprap.ReprapException;

/**
 *
 */
public abstract class IncomingMessage {

	/**
	 * The actual content portion of a packet, not the frilly bits
	 */
	private byte [] payload;
	
	/**
	 * 
	 */
	IncomingContext incomingContext;
	
	/**
	 *
	 */
	public class InvalidPayloadException extends ReprapException {
		private static final long serialVersionUID = -5403970405132990115L;
		public InvalidPayloadException() {
			super();
		}
		public InvalidPayloadException(String arg) {
			super(arg);
		}
	}
	
	/**
	 * Receive a message matching context criteria
	 * @param incomingContext the context in which to receive messages
	 * @throws IOException 
	 */
	public IncomingMessage(IncomingContext incomingContext) throws IOException {
		this.incomingContext = incomingContext;
		Communicator comm = incomingContext.getCommunicator();
		comm.receiveMessage(this);
	}

	/**
	 * Send a given message and return the incoming response.  Re-try
	 * if there is a comms problem.
	 * @param message
	 * @throws IOException
	 */
	public IncomingMessage(Device device, OutgoingMessage message, long timeout) throws IOException {
		Communicator comm = device.getCommunicator();
		for(int i=0;i<3;i++) {	// Allow 3 retries.
			//System.out.println("Retry: " + i);
			incomingContext = comm.sendMessage(device, message);
			try {
				comm.receiveMessage(this, timeout);
			} catch (IOException e) {
				e.printStackTrace();
				System.err.println("IO error/timeout, resending");
				// Just to prevent any unexpected spinning
				try {
					Thread.sleep(1);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				continue;
			}
			return;
		}
		// If it's not going to respond, try to continue regardless.
		System.err.println("Resend limit exceeded. Failing without reported error.");
	}

	
	/**
	 * Implemented by subclasses to allow them to indicate if they
	 * understand or expect a given packetType.  This is used to
	 * decide if a received packet should be accepted or possibly discarded. 
	 * @param packetType the type of packet to receive
	 * @return true if the packetType matches what is expected
	 */
	protected abstract boolean isExpectedPacketType(byte packetType);
	
	/**
	 * @return payload
	 */
	public byte[] getPayload() {
		return payload;
	}

	/**
	 * Called by the framework to provide data to the IncomingMessage.
	 * This should not normally be called by a user. 
	 * @param payload The completed message to insert into the IncomingMessage
	 * @return true is the data was accepted, otherwise false.
	 */
	public boolean receiveData(byte [] payload) {
		// We assume the packet was for us, etc.  But we need to
		// know it contains the correct contents
		if (payload == null || payload.length == 0)
			return false;
		if (isExpectedPacketType(payload[0])) {
			this.payload = (byte[])payload.clone();
			return true;
		} else {
			// That's not what we were after, so discard and wait for more
			return false;
		}
	}
	
}
