package madynes.flowoid.exporter;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;

public abstract class DataRecord implements Exportable {
	private ArrayList<DataField> dataFields;
	private TemplateRecord template;
	private ArrayList<DataField> netFlowKeyPartFields;
	private Date expirationTime;
	private boolean endFlag;
	private byte[] lastHeaders;
	
	/**
	 * Defines a data record corresponding to a NetFlow.
	 * See RFC3954 5.3 > Record N - Field Value M.
	 * 
	 * @param template template record corresponding to this record,
	 * with all of the mandatory fields defined inside
	 */
	public DataRecord(TemplateRecord template) {
		this.template = template;
		expirationTime = new Date(new Date().getTime() + ExporterService.getPrefNetflowTimeout() * 1000);
		dataFields = new ArrayList<DataField>();
		netFlowKeyPartFields = new ArrayList<DataField>();
		lastHeaders = new byte[ExporterService.getPrefMaxHeadersSize()];
		endFlag = false;
	}
	
	/**
	 * Associates the next unassociated template field defined in the record template
	 * with a data record. This one will MUST return a value with the same size of
	 * the field template definition.
	 * 
	 * @param dataField data field to associate with the next unassociated field template
	 * @param netFlowKeyPart true if this field is discriminant for the record/NetFlow. If
	 * false this field will never be considered to compare records for the integration of
	 * a new packet in an existent NetFlow.
	 */
	public void associateTemplateWithData(DataField dataField, boolean netFlowKeyPart) {
		TemplateField templateField = template.getNthTemplateField(dataFields.size());
		dataField.setTemplate(templateField);
		
		dataFields.add(dataField);
		
		if(netFlowKeyPart)
			netFlowKeyPartFields.add(dataField);
	}

	/**
	 * Returns the list of data fields defined inside this data record.
	 * 
	 * @return list of data fields
	 */
	public ArrayList<DataField> getDataFields() {
		return dataFields;
	}
	
	/**
	 * Returns the date when this record SHOULD expire. In fact, a routine will
	 * check it at regular time to force this record to be exported even though
	 * the end condition is not met. Useful for non-connected protocols (e.g. UDP)
	 * and long term connections (e.g. big TCP downloads).
	 * 
	 * @return date of expiration 
	 */
	public Date getExpirationTime() {
		return expirationTime;
	}
	
	/**
	 * Forces NetFlow expiration.
	 * 
	 * @param time time remaining to life
	 */
	public void forceExpiration(int time) {
		expirationTime = new Date(new Date().getTime() + time * 1000);
	}

	/**
	 * Calls the onInit() function of each NetFlow key part field.
	 * This function is called for every new packet sniffed, in order to
	 * create a minimal corresponding NetFlow, in order to check if
	 * a similar NetFlow/record already exists. 
	 * 
	 * @param headers raw headers of the sniffed packet 
	 */
	public void initNetFlowKeyPartFields(byte[] headers) {
		for(DataField field : netFlowKeyPartFields) {
			field.onInit(headers);
		}
	}
	
	/**
	 * Calls the onInit function of fields that are not considered by
	 * initNetFlowKeyPartFields(byte[]). Useful to complete the initialization
	 * of a new NetFlow/record.
	 * 
	 * @param headers raw headers of the sniffed packet
	 */
	public void initNoNetFlowKeyPartFields(byte[] headers) {
		for(DataField field : dataFields) {
			if(!netFlowKeyPartFields.contains(field)) {
				field.onInit(headers);
			}
		}
	}
	
	/**
	 * Calls the onUpdate() function of every field of this record. Each time
	 * a new sniffed packet is associated to this NetFlow/record, this
	 * function is called. 
	 * 
	 * @param headers raw headers of the sniffed packet 
	 */
	public void updateFields(byte[] headers) {
		for(DataField field : dataFields) {
			field.onUpdate(headers);
		}
	}
	
	/**
	 * Calls the onExport() function of every field of this record, just before
	 * to send the export packet containing this NetFlow/record.
	 */
	public void exportFields() {
		for(DataField field : dataFields) {
			field.onExport(lastHeaders);
		}
	}
	
	/**
	 * Returns the template record associated with this record.
	 * 
	 * @return template record associated
	 */
	public TemplateRecord getTemplate() {
		return template;
	}
	
	/**
	 * Returns the NetFlow ID of this record. This ID allows to
	 * establish if a new packet MUST be adding to this NetFlow/record.
	 * It's composed with the value of the NetFlow key part fields. 
	 * 
	 * @param salt arbitrary string to preserve the uniqueness of IDs.
	 * A good practice is to use a different value by driver/low protocol.
	 * The template record ID of the ExporterService registry SHOULD be used.
	 * @return an object representative of this record which can be used in
	 * a hash table
	 */
	public Object getRecordID(int salt) {
		ArrayList<Byte> id = new ArrayList<Byte>();
		
		for(byte b : String.valueOf(salt).getBytes())
			id.add(b);
		
		for(DataField keyPart : netFlowKeyPartFields) {
			ByteBuffer value = keyPart.getValue();
			int limit = value.limit();
			
			for(int i = 0; i < limit; i++) {
				id.add(keyPart.getValue().get(i));
			}
		}

		return id;
	}
	
	/**
	 * Flags this record as ended.
	 */
	public void flagAsEnded() {
		endFlag = true;
	}
	
	/**
	 * Returns true if this record was flagged as ended.
	 * 
	 * @return true if flagged ended
	 */
	public boolean isFlaggedEnded() {
		return endFlag;
	}
	
	/**
	 * Sets the headers of the last packet received.
	 * See getLastHeaders(). 
	 * 
	 * @param headers headers of the current sniffed packet
	 */
	public void setLastHeaders(byte[] headers) {
		lastHeaders = headers.clone();
	}
	
	/**
	 * Gets the headers of the last packet received. If this record
	 * was flagged ended it's the end packet.
	 * E.g. in the case of a TCP connection, if the record is ended
	 * it's the packet containing a FIN or RST flag. And not the least
	 * packet (final ACK).
	 * E.g. in the case of an UDP connection, if the record is ended
	 * it's the least packet before the final time out.
	 * 
	 * @return headers of the end packet
	 */
	public byte[] getLastHeaders() {
		return lastHeaders;
	}

	@Override
	public ArrayList<Byte> toBytes() throws TemplateMismatchException {
		ArrayList<Byte> bytes = new ArrayList<Byte>();
		
		if(dataFields.size() != template.getCount())
			throw new TemplateMismatchException("Not enough fields to fulfill"
					+ " the record template needs.");
		
		for(DataField field : dataFields) {
			bytes.addAll(field.toBytes());
		}

		return bytes;
	}

	@Override
	public short getTotalLength() {
		short length = 0;
		
		for(DataField field : dataFields) {
			length += field.getTotalLength();
		}
		
		return (short) length;
	}
	
	/**
	 * Returns true if this packet is an end marker.
	 * Useful for TCP connections.
	 * 
	 * @param headers headers of the current sniffed packet
	 * @return true if this packet is an end marker
	 */
	abstract public boolean isEnded(byte[] headers);
}
