package shared;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;

public class Message implements Serializable {
	
	private static final long serialVersionUID = -2815116531203592121L;

	protected String m_type;
	protected Hashtable<String, Object> m_attributes;
	
	public Message(String type) {
		m_type = (type == null) ? "" : type;
		m_attributes = new Hashtable<String, Object>();
	}
	
	// sets the message type
	public void setType(String type) {
		m_type = (type == null) ? "" : type;
	}
	
	// returns the message type
	public String getType() {
		return m_type;
	}
	
	// returns the number of attributes
	public int numberOfAttributes() {
		return m_attributes.size();
	}
	
	// returns the keys for the message's attributes
	public Enumeration<String> getKeys() {
		return m_attributes.keys();
	}
	
	// returns the value of a specified attribute of the message
	public Object getAttribute(String key) {
		if(key == null) { return null; }
		
		return m_attributes.get(key);
	}
	
	// sets the value of a specified attribute of the message
	public void setAttribute(String key, Object attribute) {
		if(key == null || key.length() == 0 || attribute == null) { return; }
		
		m_attributes.put(key, attribute);
	}
	
	// clears all message attributes
	public void clearAttributes() {
		m_attributes.clear();
	}
	
	// copies all attributes from another message to the current message
	public void copyAttributesFrom(Message m) {
		if(m == null) { return; }
		
		Enumeration<?> enumeration = m.m_attributes.keys();
		
		// loop over all attributes in the specified message
		String key;
		Object value;
	    while(enumeration.hasMoreElements()) {
	    	// get the attribute name and value from the specified message
	    	key = (String) enumeration.nextElement();
	    	value = m.m_attributes.get((String) key);
	    	
	    	// set the attribute locally
			setAttribute(key, value);
	    }
	}
	
	// serializes the message into a byte array
	public static byte[] serializeMessage(Message m) throws IOException {
		if(m == null) { return null; }
		
		// initialize a byte stream
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		
		// serialize the message type and write it to the stream
		byte[] typeData = Serializer.serializeString(m.getType());
		byteStream.write(Serializer.serializeInteger(typeData.length));
		byteStream.write(typeData);
		
		// initialize temporary variables
		String key;
		Object value;
		byte[] keyData, valueData;
		
		// get all of the message attribute names, and write the number of attributes to the stream
		Enumeration<String> keys = m.getKeys();
		byteStream.write(Serializer.serializeInteger(m.numberOfAttributes()));
		
		// iterate over all the attributes
		while(keys.hasMoreElements()) {
			// write the attribute name to the stream
			key = keys.nextElement();
			keyData = Serializer.serializeString(key);
			byteStream.write(Serializer.serializeInteger(keyData.length));
			byteStream.write(keyData);
			
			// write the attribute value to the stream
			value = m.getAttribute(key);
			valueData = Serializer.serializeObject(value);
			byteStream.write(Serializer.serializeInteger(valueData.length));
			byteStream.write(valueData);
		}
		
		return byteStream.toByteArray();
	}
	
	// de-serializes the message from a byte array
	public static Message deserializeMessage(byte[] data) throws IOException {
		if(data == null) { return null; }
		
		// initialize temporary variables
		byte[] temp;
		int length;
		ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
		String type;
		byte[] typeData;
		
		// read the length of the message type
		temp = new byte[4];
		byteStream.read(temp);
		length = Serializer.deserializeInteger(temp);
		
		// read the message type
		if(length == 0) {
			type = "";
		}
		else {
			typeData = new byte[length];
			byteStream.read(typeData);
			type = Serializer.deserializeString(typeData);
		}
		
		// initialize more temporary variables
		String key;
		Object value;
		int numberOfAttributes;
		byte[] keyData, valueData;
		
		// read the number of attributes
		temp = new byte[4];
		byteStream.read(temp);
		numberOfAttributes = Serializer.deserializeInteger(temp);
		Hashtable<String, Object> attributes = new Hashtable<String, Object>();
		
		// read all the attributes
		for(int i=0;i<numberOfAttributes;i++) {
			// read the length of the attribute name
			temp = new byte[4];
			byteStream.read(temp);
			length = Serializer.deserializeInteger(temp);
			
			// read the attribute name
			if(length == 0) {
				key = "";
			}
			else {
				keyData = new byte[length];
				byteStream.read(keyData);
				key = Serializer.deserializeString(keyData);
			}
			
			// read the length of the attribute value
			temp = new byte[4];
			byteStream.read(temp);
			length = Serializer.deserializeInteger(temp);
			
			// read the attribute value
			if(length == 0) {
				value = null;
			}
			else {
				valueData = new byte[length];
				byteStream.read(valueData);
				value = Serializer.deserializeObject(valueData);
			}
			
			// read and store the attribute
			if(key == null || value == null || key.length() == 0) { continue; }
			attributes.put(key, value);
		}
		
		// create the message, and set its attributes
		Message m = new Message(type);
		m.m_attributes = attributes;
		
		return m;
	}
}
