package madynes.flowoid.exporter;

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


public class TemplateField implements Exportable {
	private final short fieldSize = 4;   
	private TypeDefinition type;
	private short length;
	
	public static class InadequateLengthException extends Exception {
		private static final long serialVersionUID = 1L;
		private final String message;
		
		public InadequateLengthException(String message) {
			this.message = message;
		}
		
		public String getMessage() {
			return message;
		}
	}
	
	/**
	 * @See RFC3954 6.2.8.
	 */
	public static enum TypeDefinition {
		IN_BYTES						( 1, 0),
		IN_PKTS							( 2, 0),
		FLOWS							( 3, 0),
		PROTOCOL						( 4, 1),
		TOS								( 5, 1),
		TCP_FLAGS						( 6, 1),
		L4_SRC_PORT						( 7, 2),
		IPV4_SRC_ADDR					( 8, 4),
		SRC_MASK						( 9, 1),
		INPUT_SNMP						(10, 0),
		L4_DST_PORT						(11, 2),
		IPV4_DST_ADDR					(12, 4),
		DST_MASK						(13, 1),
		OUTPUT_SNMP						(14, 0),
		IPV4_NEXT_HOP					(15, 4),
		SRC_AS							(16, 0),
		DST_AS							(17, 0),
		BGP_IPV4_NEXT_HOP				(18, 4),
		MUL_DST_PKTS					(19, 0),
		MUL_DST_BYTES					(20, 0),
		LAST_SWITCHED					(21, 4),
		FIRST_SWITCHED					(22, 4),
		OUT_BYTES						(23, 0),
		OUT_PKTS						(24, 0),
		IPV6_SRC_ADDR					(27, 16),
		IPV6_DST_ADDR					(28, 16),
		IPV6_SRC_MASK					(29, 1),
		IPV6_DST_MASK					(30, 1),
		IPV6_FLOW_LABEL					(31, 3),
		ICMP_TYPE						(32, 2),
		MUL_IGMP_TYPE					(33, 1),
		SAMPLING_INTERVAL				(34, 4),
		SAMPLING_ALGORITHM				(35, 1),
		FLOW_ACTIVE_TIMEOUT				(36, 2),
		FLOW_INACTIVE_TIMEOUT			(37, 2),
		ENGINE_TYPE						(38, 1),
		ENGINE_ID						(39, 1),
		TOTAL_BYTES_EXP					(40, 0),
		TOTAL_PKTS_EXP					(41, 0),
		TOTAL_FLOWS_EXP					(42, 0),
		MPLS_TOP_LABEL_TYPE				(46, 1),
		MPLS_TOP_LABEL_IP_ADDR			(47, 4),
		FLOW_SAMPLER_ID					(48, 1),
		FLOW_SAMPLER_MODE				(49, 1),
		FLOW_SAMPLER_RANDOM_INTERVAL	(50, 4),
		DST_TOS							(55, 1),
		SRC_MAC							(56, 6),
		DST_MAC							(57, 6),
		SRC_VLAN						(58, 2),
		DST_VLAN						(59, 2),
		IP_PROTOCOL_VERSION				(60, 1),
		DIRECTION						(61, 1),
		IPV6_NEXT_HOP					(62, 16),
		BGP_IPV6_NEXT_HOP				(63, 16),
		IPV6_OPTION_HEADERS				(64, 4),
		MPLS_LABEL_1					(70, 3),
		MPLS_LABEL_2					(71, 3),
		MPLS_LABEL_3					(72, 3),
		MPLS_LABEL_4					(73, 3),
		MPLS_LABEL_5					(74, 3),
		MPLS_LABEL_6					(75, 3),
		MPLS_LABEL_7					(76, 3),
		MPLS_LABEL_8					(77, 3),
		MPLS_LABEL_9					(78, 3),
		MPLS_LABEL_10					(79, 3),
		
		// RFC3954 6.2.8 §1
		PROPRIETARY_1					(25, 0),
		PROPRIETARY_2					(26, 0),
		PROPRIETARY_3					(43, 0),
		PROPRIETARY_4					(44, 0),
		PROPRIETARY_5					(45, 0),
		PROPRIETARY_6					(51, 0),
		PROPRIETARY_7					(52, 0),
		PROPRIETARY_8					(53, 0),
		PROPRIETARY_9					(54, 0),
		PROPRIETARY_10					(65, 0),
		PROPRIETARY_11					(66, 0),
		PROPRIETARY_12					(67, 0),
		PROPRIETARY_13					(68, 0),
		PROPRIETARY_14					(69, 0),
		
		// OUT OF STANDARDS
		MADYNES_LOC_ALGO				(347, 1),
		MADYNES_LOC_TIMESTAMP			(348, 4),
		MADYNES_LOC_LAT_INT				(349, 4),
		MADYNES_LOC_LAT_DEC				(350, 8),
		MADYNES_LOC_LONG_INT			(351, 4),
		MADYNES_LOC_LONG_DEC			(352, 8),
		MADYNES_DEVICE_ID				(353, 4),
		MADYNES_APP_NAME				(354, 0);
		
		private int value, length;
		
		TypeDefinition(int value, int length) {
			this.value = value;
			this.length = length;
		}
		
		public int getValue() {
			return value;
		}
		
		public int getLength() {
			return length;
		}
	}

	/**
	 * Defines a template field, part of a NetFlow/record format.
	 * See RFC3954 5.2 > Field Type / Field Length.
	 * 
	 * @param type Standardized type of field.
	 * @throws InadequateLengthException according to the RFC this type of field has not
	 * a fixed length so you MUST define it with the alternative constructor
	 */
	public TemplateField(TypeDefinition type) throws InadequateLengthException {
		if(type.getLength() == 0) {
			
			// RFC3954 6.2.8 §3
			throw new InadequateLengthException("A length MUST be specified when "
				+ "a 0-size (undefined) field type is used.");
		}

		this.type = type;
		length = (short) type.getLength();
	}
	
	/**
	 * See TemplateField(TypeDefinition).
	 * Allows to specify a variable length.
	 * See RFC3954 6.2.8 §3.
	 * 
	 * @param type see TemplateField(TypeDefinition) 
	 * @param length length of the value corresponding to this type of field
	 * @throws InadequateLengthException according to the RFC this type has a fixed length
	 * so you CANNOT redefine it. Use the alternative constructor
	 */
	public TemplateField(TypeDefinition type, int length) throws InadequateLengthException {
		if(type.getLength() != 0) {
			
			throw new InadequateLengthException("A length MUST NOT be specified when "
				+ " a not-0-size (fixed) field type is used.");
		}
		
		this.type = type;
		this.length = (short) length;
	}

	/**
	 * Returns the value length imposed by this template. 
	 * 
	 * @return value length
	 */
	public short getLength() {
		return (short) length;
	}
	
	@Override
	public short getTotalLength() {
		return fieldSize;
	}
	
	/**
	 * Returns the standardized ID of this template.   
	 * 
	 * @return standardized ID of this template
	 */
	public short getValue() {
		return (short) type.getValue();
	}

	@Override
	public ArrayList<Byte> toBytes() {
		ArrayList<Byte> bytes = new ArrayList<Byte>();
		
    	ByteBuffer field = ByteBuffer.allocate(fieldSize);
    	field.order(ByteOrder.BIG_ENDIAN);
    	field.putShort(getValue());
    	field.putShort(getLength());
    	
    	for(byte b : field.array()) {
    		bytes.add((Byte) b);
    	}
    	
    	return bytes;
	}
}