package protocolP2P;
import protocolP2P.RequestResponseCode;
import protocolP2P.FilePart;
import protocolP2P.FileList;
import protocolP2P.LoadRequest;
import protocolP2P.HashRequest;
import protocolP2P.HashResponse;
import protocolP2P.DiscoverRequest;
import protocolP2P.DiscoverResponse;
import protocolP2P.Register;
import protocolP2P.Unregister;
import protocolP2P.RatioRequest;
import protocolP2P.RatioResponse;
import protocolP2P.UpdateRatio;
import protocolP2P.Denied;
import protocolP2P.SizeRequest;
import protocolP2P.SizeResponse;
import localException.ProtocolError;
import localException.InternalError;
import localException.TransmissionError;
import localException.SizeError;
import tools.BytesArrayTools;
/** Representation of payload. If payload has a size, use subclasses instead.
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public class Payload {
	protected RequestResponseCode requestResponseCode;
	protected final static int PAYLOAD_SIZE_POSITION = 4;
	protected final static int PAYLOAD_START_POSITION = 8;

	/** Consructor used to create Payload with a payload size of zero using a RRCode.
	 * @param requestResponseCode	Request/Response code associated with the payload
	 * @throws	InternalError
	 */
	public Payload(RequestResponseCode requestResponseCode) throws InternalError {
		/* asserts to help debugging */
		assert requestResponseCode != RequestResponseCode.LIST_RESPONSE     || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class";
		assert requestResponseCode != RequestResponseCode.LOAD_RESPONSE     || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class";
		assert requestResponseCode != RequestResponseCode.LOAD_REQUEST      || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class";
		assert requestResponseCode != RequestResponseCode.HASH_REQUEST      || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class";
		assert requestResponseCode != RequestResponseCode.HASH_RESPONSE     || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class";
		assert requestResponseCode != RequestResponseCode.DISCOVER_REQUEST  || (this instanceof DiscoverRequest) : "DISCOVER_REQUEST must use DiscoverRequest class";
		assert requestResponseCode != RequestResponseCode.DISCOVER_RESPONSE || (this instanceof DiscoverResponse) : "DISCOVER_RESPONSE must use DiscoverResponse class";
		assert requestResponseCode != RequestResponseCode.REGISTER          || (this instanceof Register) : "REGISTER must use Register class";
		assert requestResponseCode != RequestResponseCode.UNREGISTER        || (this instanceof Unregister) : "UNREGISTER must use Unregister class";
		assert requestResponseCode != RequestResponseCode.RATIO_REQUEST     || (this instanceof RatioRequest) : "RATIO_REQUEST must use RatioRequest class";
		assert requestResponseCode != RequestResponseCode.RATIO_RESPONSE    || (this instanceof RatioResponse) : "RATIO_RESPONSE must use RatioResponse class";
		assert requestResponseCode != RequestResponseCode.UPDATE_RATIO      || (this instanceof UpdateRatio) : "UPDATE_RATIO must use UpdateRatio class";
		assert requestResponseCode != RequestResponseCode.DENIED            || (this instanceof Denied) : "DENIED must use Denied class";
		assert requestResponseCode != RequestResponseCode.SIZE_REQUEST      || (this instanceof SizeRequest) : "SIZE_REQUEST must use SizeRequest class";
		assert requestResponseCode != RequestResponseCode.SIZE_RESPONSE     || (this instanceof SizeResponse) : "SIZE_RESPONSE must use SizeResponse class";
		this.requestResponseCode = requestResponseCode;
		checkRequestResponseCode(); // this can throw InternalError
	}

	/** Constructor used to create a Payload (when no more specific subclasses exists) using packet as parameter.
	 * If payload size is not empty, using subclass is required.
	 * @param packet	the full packet received
	 * @throws	ProtocolError
	 * @throws	InternalError
	 * @throws	TransmissionError
	 * @throws	SizeError
	 */
	protected Payload(byte[] packet) throws SizeError, ProtocolError, InternalError, TransmissionError {
		/* asserts to help debugging */
		assert getPayloadSize(packet) + PAYLOAD_START_POSITION <= packet.length : "Payload is truncated";
		if (packet.length < getPayloadSize(packet) + PAYLOAD_START_POSITION) {
			throw new TransmissionError();
		}
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LIST_RESPONSE     || (this instanceof FileList) : "LIST_RESPONSE must use FilePart class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_RESPONSE     || (this instanceof FilePart) : "LOAD_RESPONSE must use FileList class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.LOAD_REQUEST      || (this instanceof LoadRequest) : "LOAD_REQUEST must use LoadRequest class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.HASH_REQUEST      || (this instanceof HashRequest) : "HASH_REQUEST must use HashRequest class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.HASH_RESPONSE     || (this instanceof HashResponse) : "HASH_RESPONSE must use HashResponse class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DISCOVER_REQUEST  || (this instanceof DiscoverRequest) : "DISCOVER_REQUEST must use DiscoverRequest class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DISCOVER_RESPONSE || (this instanceof DiscoverResponse) : "DISCOVER_RESPONSE must use DiscoverResponse class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.REGISTER          || (this instanceof Register) : "REGISTER must use Register class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.UNREGISTER        || (this instanceof Unregister) : "UNREGISTER must use Unregister class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.RATIO_REQUEST     || (this instanceof RatioRequest) : "RATIO_REQUEST must use RatioRequest class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.RATIO_RESPONSE    || (this instanceof RatioResponse) : "RATIO_RESPONSE must use RatioResponse class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.UPDATE_RATIO      || (this instanceof UpdateRatio) : "UPDATE_RATIO must use UpdateRatio class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.DENIED            || (this instanceof Denied) : "DENIED must use Denied class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.SIZE_REQUEST      || (this instanceof SizeRequest) : "SIZE_REQUEST must use SizeRequest class";
		assert RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]) != RequestResponseCode.SIZE_RESPONSE     || (this instanceof SizeResponse) : "SIZE_RESPONSE must use SizeResponse class";
		requestResponseCode = RequestResponseCode.fromCode(packet[RequestResponseCode.RRCODE_POSITION]);
		checkRequestResponseCode(); // this can throw InternalError
	}

	/** Used to check RRCode used is compatible with this class use, or if a more specific subclass is required.
	 * @throws	InternalError
	 */
	private void checkRequestResponseCode() throws InternalError {
		/* Incorrect use cases (use subclasses instead) */
		if ((requestResponseCode == RequestResponseCode.LIST_RESPONSE     && !(this instanceof FileList))
		 || (requestResponseCode == RequestResponseCode.LOAD_RESPONSE     && !(this instanceof FilePart))
		 || (requestResponseCode == RequestResponseCode.LOAD_REQUEST      && !(this instanceof LoadRequest))
		 || (requestResponseCode == RequestResponseCode.HASH_REQUEST      && !(this instanceof HashRequest))
		 || (requestResponseCode == RequestResponseCode.HASH_RESPONSE     && !(this instanceof HashResponse))
		 || (requestResponseCode == RequestResponseCode.DISCOVER_REQUEST  && !(this instanceof DiscoverRequest))
		 || (requestResponseCode == RequestResponseCode.DISCOVER_RESPONSE && !(this instanceof DiscoverResponse))
		 || (requestResponseCode == RequestResponseCode.REGISTER          && !(this instanceof Register))
		 || (requestResponseCode == RequestResponseCode.UNREGISTER        && !(this instanceof Unregister))
		 || (requestResponseCode == RequestResponseCode.RATIO_REQUEST     && !(this instanceof RatioRequest))
		 || (requestResponseCode == RequestResponseCode.RATIO_RESPONSE    && !(this instanceof RatioResponse))
		 || (requestResponseCode == RequestResponseCode.UPDATE_RATIO      && !(this instanceof UpdateRatio))
		 || (requestResponseCode == RequestResponseCode.DENIED            && !(this instanceof Denied))
		 || (requestResponseCode == RequestResponseCode.SIZE_REQUEST      && !(this instanceof SizeRequest))
		 || (requestResponseCode == RequestResponseCode.SIZE_RESPONSE     && !(this instanceof SizeResponse))
		) {
			throw new InternalError();
		}
	}

	/** Returns a byte[] containing Packet with padding.
	 * This Packet is still incomplete and should not be send directly.
	 * ProtocolP2PPacket will use this method to generate the complete Packet.
	 * @return Packet with padding
	 * @throws	InternalError
	 */
	protected byte[] toPacket() throws InternalError {
		// InternalError is impossible in this method on Payload class but still on subclasses
		byte [] packet = new byte[8]; // java initialize all to zero
		// size is zero (and this is the default)
		// set request/response code
		packet[RequestResponseCode.RRCODE_POSITION] = requestResponseCode.codeValue;
		// bits 16-31 are reserved for future use
		// payload size is 0 (this is what java have initialized Packet)
		return packet;
	}

	/** Set payload’s size in a Packet.
	 * @param size	integer representing payload size
	 * @param packet	Packet to be completed
	 * @throws	InternalError
	 */
	protected static void setPayloadSize(int size, byte[] packet) throws InternalError {
		/* assert to help debugging */
		assert size >= 0: "Payload size cannot be negative";
		if (size < 0) {
			// We don't throw SizeError
			// because this is only for reception side
			throw new InternalError();
		}
		BytesArrayTools.write(packet, PAYLOAD_SIZE_POSITION, size);
	}

	/** Get payload’s size from a Packet.
	 * @param packet	the full Packet received
	 * @return	integer representing payload size
	 * @throws	SizeError
	 */
	protected static int getPayloadSize(byte[] packet) throws SizeError {
		return BytesArrayTools.readInt(packet, PAYLOAD_SIZE_POSITION);
	}

	/** RRCode getter.
	 * @return	Request/Response code
	 */
	public RequestResponseCode getRequestResponseCode() {
		return requestResponseCode;
	}
}
