/**
 * @author	STF 424_ITS_Test_Platform
 * @version    $URL$
 *             $Id$
 */
package org.etsi.its.codec.ttcn.LibItsGeoNetworking_TypesAndValues;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.regex.Matcher;

import org.etsi.codec.DecodingContext;
import org.etsi.codec.EncodingContext;
import org.etsi.codec.TriMessageImpl;
import org.etsi.common.ByteHelper;
import org.etsi.its.codec.ItsCodecImpl;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.ttcn.tci.BitstringValue;
import org.etsi.ttcn.tci.EnumeratedValue;
import org.etsi.ttcn.tci.IntegerValue;
import org.etsi.ttcn.tci.RecordValue;
import org.etsi.ttcn.tci.TciCDProvided;
import org.etsi.ttcn.tci.TciCDRequired;
import org.etsi.ttcn.tci.TciTypeClass;
import org.etsi.ttcn.tci.Type;
import org.etsi.ttcn.tci.UnionValue;
import org.etsi.ttcn.tci.Value;
import org.etsi.ttcn.tri.TriMessage;

/** 
 * This class provides the TTCN-3 codec implementation for LibItsGeoNetworking_TypesAndValuesCodec TTCN-3 module (ITS message primitives)
 * 
 * Note that it requires closely linked to LibItsGeoNetworking_TypesAndValues TTCN-3 codec
 * 
 * See 'ETSI TS 102 871-3 Clause 4.4.1 Primitives of the geoNetworkingPort' for message primitives on geoNetworkingPort port
 * See 'ETSI TS 102 871-3 Clause 4.4.3	Primitives of the taPort' for message primitives on taPort port
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsGeoNetworking_TypesAndValuesCodec extends ItsCodecImpl {

	/**
	 * Constructor
	 * 
	 * @param typeServer Reference to the tool specific codec implementation
	 */
	public LibItsGeoNetworking_TypesAndValuesCodec(TciCDRequired typeServer) {
		super(typeServer);

		_logger.entering(this.toString(), "Constructor", String.format("version: %s - ", Version) + this);
	} // End of Constructor

	/** 
	 * Returns an encoded TriMessage according to the encoding rules.
	 * This operation shall be called whenever the TE has to encode a Value
	 * 
	 * @param template The value to be encoded
	 * @return Returns an encoded TriMessage for the specified encoding rule
	 * 
	 * See ETSI ES 201 873-6 V4.2.1 - 7.3.2.2.2 encode
	 */
	@Override
	public TriMessage encode(final Value template) {
		_logger.entering(this.toString(), "encode", template.toString());

		try {
			TriMessage encoded = super.encode(template);
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsGeoNetworking_TypesAndValues.encode - " + this.toString() + ": ", encoded.getEncodedMessage());
			return encoded;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	} // End of method encode

	/** 
	 * This operation decodes message according to the encoding rules and returns a TTCN-3 value. 
	 * The decodingHypothesis shall be used to determine whether the encoded value can be decoded. 
	 * If an encoding rule is not self-sufficient, i.e. if the encoded message does not inherently 
	 * contain its type decodingHypothesis shall be used. If the encoded value can be decoded without 
	 * the decoding hypothesis, the distinct null value shall be returned if the type determined from 
	 * the encoded message is not compatible with the decoding hypothesis
	 * 
	 * @param rcvdMessage The encoded message to be decoded
	 * @param decodingHypothesis The hypothesis the decoding can be based on
	 * @return Returns the decoded value, if the value is of a compatible type as the decodingHypothesis, else the distinct value null
	 * 
	 * See ETSI ES 201 873-6 V4.2.1 - 7.3.2.2.1 decode
	 */
	@Override
	public Value decode(final TriMessage rcvdMessage, final Type decodingHypothesis) {
		_logger.entering(this.toString(), "decode", decodingHypothesis.getName());

		try {
			// Initialise the decodingContext
			byte[] encodedMsg = rcvdMessage.getEncodedMessage();
			if (encodedMsg.length == 0) {
				_logger.severe("encodedMsg is empty");
				return null;
			}
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsGeoNetworking_TypesAndValues.decode: ", encodedMsg);
			DecodingContext decodingContext = new DecodingContext(encodedMsg, decodingHypothesis.getTypeEncoding());
			
			// Check which type class the decoding hypothesis is of
			Value value = decodeTLVLayer(decodingHypothesis, decodingContext); // This is a Ut/Ac message
			_logger.exiting(this.toString(), "decode", value.toString());
			return value;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		
		return null;
	} // End of method decode

	/**
	 * This method checks the present subfield-type to call the representing method
	 * 
	 * @param encodingContext The context to encode the TTCN-3 message.
	 * @param rv The <code>RecordValue</code> from getting subfields.
	 */
	@Override
	public void encodeRecordValue(EncodingContext encodingContext, final RecordValue rv) {
		_logger.entering(this.toString(), "encodeRecordValue", String.format("%s - %s", rv.getType().getName(), (rv.notPresent() == true) ? "not present" : "present"));

		if (rv == null) {
			return;
		}

		if (rv.getValueEncoding() != "LibItsGeoNetworking_TypesAndValues") {
			// Retrieve the correct codec
			TciCDProvided codec = getCodec(rv.getValueEncoding());
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", rv.getValueEncoding()));
			}
			// And continue encoding operations
			TriMessage encoded = codec.encode(rv);
			try {
				encodingContext.setBytes(encoded.getEncodedMessage());
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else if ( // FIXME To be enhance because some code should be factorised
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcGetLongPosVector.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStartBeaconing.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStartPassBeaconing.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStartBeaconingMultipleNeighbour.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStopBeaconing.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStopPassBeaconing.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.AcStopBeaconingMultipleNeighbour.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoUnicastMessage.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoAnycastMessage.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoBroadcastMessage.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateSHBMessage.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateTSBMessage.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.ChangePosition.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GeoNetworkingPacket.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.GeoArea.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.Area.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.UtGNInitialize.name())
				) {
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(rv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			if (rv.notPresent()) {
				try { // Length is 0
					encodingContext.setByte((byte) 0x00);
					encodingContext.setByte((byte) 0x00);
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				EncodingContext out_rv = new EncodingContext();
				if (rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoUnicastMessage.name()) {
					// Encode gnAddress
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[0]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_rv1 = new EncodingContext();
					super.encodeRecordValue(out_rv1, (RecordValue) rv.getField(rv.getFieldNames()[0])); // Value
					try { // Length
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					// Encode lifetime if present
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[1]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					out_rv1 = new EncodingContext();
					super.encodeIntegerValue(out_rv1, (IntegerValue) rv.getField(rv.getFieldNames()[1])); // Value
					try {
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					} // Length
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					// Encode payload if present
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[2]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					out_rv1 = new EncodingContext();
					super.encodeUnionValue(out_rv1, (UnionValue) rv.getField(rv.getFieldNames()[2])); // Value
					try { // Length
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} else if (
					(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoAnycastMessage.name()) || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateGeoBroadcastMessage.name())) {
					// Encode GeoArea
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[0]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_rv1 = new EncodingContext();
					encodeRecordValue(out_rv1, (RecordValue) rv.getField(rv.getFieldNames()[0])); // Value
					try { // Length
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					// Encode lifetime if present
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[1]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					out_rv1 = new EncodingContext();
					super.encodeIntegerValue(out_rv1, (IntegerValue) rv.getField(rv.getFieldNames()[1])); // Value
					try {
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					} // Length
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					// Encode payload if present
					try {
						out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[2]).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					out_rv1 = new EncodingContext();
					super.encodeUnionValue(out_rv1, (UnionValue) rv.getField(rv.getFieldNames()[2])); // Value
					try {
						out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
					} catch (IOException e1) {
						e1.printStackTrace();
					} // Length
					if (out_rv1.size() != 0) { // Value
						try {
							out_rv.setBytes(out_rv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} else if (
						(rv.getType().getName() == TestAdapterMessageTypeEnum.GeoArea.name()) || 
						(rv.getType().getName() == TestAdapterMessageTypeEnum.Area.name())
					) {
					for (String fieldName : rv.getFieldNames()) {
						Value fieldValue = rv.getField(fieldName);
						if (!fieldValue.notPresent()) {
							EncodingContext out_rv1 = new EncodingContext();
							switch (fieldValue.getType().getTypeClass()) {
								case TciTypeClass.RECORD:
									encodeRecordValue(out_rv1, (RecordValue) fieldValue);
									break;
								case TciTypeClass.ENUMERATED:
									// no break;
								case TciTypeClass.INTEGER:
									encodeIntegerValue(out_rv1, fieldName, (IntegerValue) fieldValue);
									break;
								default:
									throw new RuntimeException(String.format("Not implemented type class %s - %s", fieldValue.getType().getName(), fieldValue.getType().getTypeClass()));
							} // End of 'switch' statement
							if (out_rv1.size() != 0) { // Value
								try {
									out_rv.setBytes(out_rv1.toByteArray());
								} catch (IOException e) {
									e.printStackTrace();
								}
							}
						} else {
							// The field is omitted
							_logger.info(String.format("Set field omit: %s - %s", fieldName, fieldValue.getType().getName()));
							try {
								out_rv.setByte(TestAdapterMessageTypeEnum.convert(fieldName)); // Type
								out_rv.setByte((byte)0x00); // Length 
								out_rv.setByte((byte)0x00);
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					} // End of 'for' statement
				} else if (
					(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateSHBMessage.name()) || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.GenerateTSBMessage.name())) {
						// Encode payload if present
						try {
							out_rv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getFieldNames()[0]).convert());
						} catch (IOException e) {
							e.printStackTrace();
						} // Type
						EncodingContext out_rv1 = new EncodingContext();
						super.encodeUnionValue(out_rv1, (UnionValue) rv.getField(rv.getFieldNames()[0])); // Value
						try {
							out_rv.setBytes((byte[])ByteHelper.intToByteArray(out_rv1.size(), LengthFieldSize));
						} catch (IOException e) {
							e.printStackTrace();
						} // Length
						if (out_rv1.size() != 0) { // Value
							try {
								out_rv.setBytes(out_rv1.toByteArray());
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
				} else if (rv.getType().getName() == TestAdapterMessageTypeEnum.ChangePosition.name()) {
					// Nothing to do
				} else {
					// Default behavior
					super.encodeRecordValue(out_rv, rv);
				}
				try {
					encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_rv.size(), LengthFieldSize));
				} catch (IOException e) {
					e.printStackTrace();
				} // Length
				if (out_rv.size() != 0) { // Value
					try {
						encodingContext.setBytes(out_rv.toByteArray());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} else { 
			// Protocol message
			super.encodeRecordValue(encodingContext, rv);
		}

//		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsGeoNetworking_TypesAndValues.encodeRecordValue: ", encodingContext.toByteArray());
	} // End of method encodeRecordValue

	/**
	 * This method checks the present subfield-type to call the representing method
	 * 
	 * @param encodingContext The context to encode the TTCN-3 message.
	 * @param uv The <code>UnionValue</code> from getting subfields.
	 */
	@Override
	public void encodeUnionValue(EncodingContext encodingContext, final UnionValue uv) {
		_logger.entering(this.toString(), "encodeUnionValue", String.format("%s - %s", uv.getType().getName(), (uv.notPresent() == true) ? "not present" : "present"));

		if (uv == null) {
			return;
		}

		if (
				(uv.getType().getName() == "AcGnPrimitive") || 
				(uv.getType().getName() == "AcStartBeaconing") || 
				(uv.getType().getName() == "AcStopBeaconing") || 
				(uv.getType().getName() == "AcStartPassBeaconing") || 
				(uv.getType().getName() == "AcStopPassBeaconing") || 
				(uv.getType().getName() == "AcStartBeaconingMultipleNeighbour") || 
				(uv.getType().getName() == "AcStopBeaconingMultipleNeighbour") || 
				(uv.getType().getName() == "UtGNEvent")) {
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(uv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			if (uv.notPresent()) {
				try { // Length is 0
					encodingContext.setByte((byte) 0x00);
					encodingContext.setByte((byte) 0x00);
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else {
				EncodingContext out_uv = new EncodingContext();
				super.encodeUnionValue(out_uv, uv);
				try { // Length
					encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_uv.size(), LengthFieldSize));
				} catch (IOException e) {
					e.printStackTrace();
				}
				if (out_uv.size() != 0) { // Value
					try {
						encodingContext.setBytes(out_uv.toByteArray());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} else if ( // The Codec shall set the length of the payload if present
				(uv.getType().getName() == "Payload") || 
				(uv.getType().getName() == "BtpPacket") || 
				(uv.getType().getName() == "Ipv6Packet")) {
			EncodingContext out_uv = new EncodingContext();
			super.encodeUnionValue(out_uv, uv);
			if (out_uv.size() != 0) {
				try {
					encodingContext.setBytes(out_uv.toByteArray());
					encodingContext.setBytesAtMark(ByteHelper.intToByteArray(out_uv.size(), 2)); // 2 byte length
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} else {
			super.encodeUnionValue(encodingContext, uv);
		}

//		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsGeoNetworking_TypesAndValues.encodeUnionValue: ", encodingContext.toByteArray());
	} // End of method encodeUnionValue

	/** 
	 * This method decodes the current TLV sequence
	 * @param decodingHypothesis Decoding hypothesis
	 * @param decodingContext The complete encoded message
	 * @return The decoded value contained in the TLV sequence
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or construct
	 */
	public Value decodeTLVLayer(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeTLVLayer", decodingHypothesis.getName());

		//Check which type class the decoding hypothesis is of
		Value value = null;
		switch (decodingHypothesis.getTypeClass()) {
			case TciTypeClass.UNION:
				// Create a new instance
				final UnionValue unionValue = (UnionValue)decodingHypothesis.newInstance();
				// And fill it
				value = decodeTLVLayer(unionValue, decodingContext);
				break;
			case TciTypeClass.RECORD:
				// Create a new instance
				final RecordValue recordValue = (RecordValue)decodingHypothesis.newInstance();
				// And fill it
				value = decodeTLVLayer(recordValue, decodingContext);
				break;
			case TciTypeClass.INTEGER:
				value = decodeIntegerValue(decodingHypothesis, decodingContext);
				break;
			default:
				throw new RuntimeException(String.format("Not implemented type class %s - %s", decodingHypothesis.getName(), decodingHypothesis.getTypeClass()));
		} // End of 'switch' statement

		return value;
	} // End of method decodeTLVLayer

	/** 
	 * This method decodes the current TLV sequence, based on unionValue
	 * @param unionValue An union value to decode
	 * @param encodedMsg The complete encoded message
	 * @return The decoded value contained in the TLV sequence
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or construct
	 * @see UnionValue
	 */
	private UnionValue decodeTLVLayer(final UnionValue unionValue, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeTLVLayer (1)", unionValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.getNextByte());
		_logger.info(String.format("(1): type:%s", type));
		if ((type == null) || (type.name() != unionValue.getType().getName())) {
			// It is not a TLV format, process it as a protocol format
			return (UnionValue)decodeProtocolLayer(unionValue.getType(), decodingContext);
		}
		// And process it
		switch (type) {
			case AcGnPrimitive:
				//No break;
			case AcGnResponse:
				decodingContext.getNextBytes(2); // Skip Length (2 bytes)
				type = TestAdapterMessageTypeEnum.valueOf(decodingContext.peekNextByte());
				for (String variantName : unionValue.getVariantNames()) {
					if (unionValue.getVariant(variantName).getType().getName() == type.name()) {
						Value value = decodeTLVLayer(unionValue.getVariant(variantName).getType(), decodingContext);
						if (value != null) {
							unionValue.setVariant(variantName, value);
						}
						return unionValue;
					}
				} // End of 'for' statement
				throw new RuntimeException(String.format("Variant not found for '%s'", unionValue.getType().getName()));
		} // End of 'switch' statement

		return null;
	} // End of method decodeTLVLayer

	/** 
	 * This method decodes the current TLV sequence, based on RecordValue
	 * @param recordValue A record value to decode
	 * @param decodingContext The complete encoded message
	 * @return The decoded value contained in the TLV sequence
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or construct
	 * @see RecordValue
	 */
	private RecordValue decodeTLVLayer(RecordValue recordValue, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeTLVLayer (2)", recordValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.getNextByte());
		// And process it
		switch (type) {
			case AcStartBeaconing:
				//No break;
			case AcStartPassBeaconing:
				//No break;
			case AcStartBeaconingMultipleNeighbour:
				//No break;
			case AcStopBeaconing:
				//No break;
			case AcStopPassBeaconing:
				//No break;
			case AcStopBeaconingMultipleNeighbour:
				//No break;
			case GeoNetworkingPacket:
				//No break;
			case AcGetLongPosVector:
				//No break;
			case AcGnResponseFailure:
				//No break;
			case LongPosVector:
				decodingContext.getNextBytes(2); // Skip Length (2 bytes)
				// Protocol message
				if (!decodingContext.isEndOfBufferReached()) {
					recordValue = (RecordValue) decodeProtocolLayer(recordValue.getType(), decodingContext);
				}
				break;
			default:
				throw new RuntimeException(String.format("(2): Unimplemented type %s", type));
		}

		return recordValue;
	} // End of method decodeTLVLayer

	/** 
	 * This method decodes a protocol layer message (CAM, BTP, DENM, GeoN)
	 * @param decodingHypothesis A record value to decode
	 * @param decodingContext The complete encoded message
	 * @return The decoded value contained in the TLV sequence
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or construct
	 */
	public Value decodeProtocolLayer(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeProtocolLayer", String.format("%s - %s", decodingHypothesis.getName(), decodingContext.toString()));

		// Sanity check
		if (decodingContext.isEndOfBufferReached()) {
			return null;
		}

		Value value = null;
		//Check which type class the decoding hypothesis is of
		switch (decodingHypothesis.getTypeClass()) {
			case TciTypeClass.UNION:
				value = decodeUnionValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.RECORD:
				value = decodeRecordValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.ENUMERATED:
				value = decodeEnumeratedValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.INTEGER:
				value = decodeIntegerValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.ANYTYPE:
				//No break;
			case TciTypeClass.OCTETSTRING:
				value = decodeOctetstringValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.BITSTRING:
				value = decodeBitstringValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.BOOLEAN:
				value = decodeBooleanValue(decodingContext, decodingHypothesis);
				break;
			default:
				throw new RuntimeException(String.format("Unimplemented record %s - %s", decodingHypothesis.getName(), decodingHypothesis.getTypeClass()));
		} // End of 'switch' statement

		return value;
	} // End of method decodeProtocolLayer

	/**
	 * This methods decodes a protocol message, based on a record data structure
	 * @param decodingHypothesis The requested type
	 * @param decodingContext The protocol message in bytes format
	 * @return A <code>RecordValue</code> instance resulting of the decoding operations
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or constructor
	 */
	private RecordValue decodeRecordValue(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeRecordValue", decodingHypothesis.getName());
		
		RecordValue recordValue = null;
		if (decodingHypothesis.getTypeEncoding() != decodingContext.getValueEncoding()) {
			// Retrieve the correct codec
			TciCDProvided codec = getCodec(decodingHypothesis.getDefiningModule().getModuleName());
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", decodingHypothesis.getDefiningModule().getModuleName()));
			}
			// And continue decoding operations
			byte[] newMessage = null;
			if (((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() == 0) {
				// Payload length was not forced by TTCN-3 test case, use bitpos to calculate the length of the payload
				newMessage = decodingContext.getRemaindingBytes();
			} else {
				// Payload length was forced by TTCN-3 test case, use this value
				newMessage = decodingContext.extractBytes(((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt()/*Length in bytes*/);
			}
			// Set the variant field
			recordValue= (RecordValue) codec.decode(new TriMessageImpl(newMessage), decodingHypothesis);
		} else {
			recordValue = (RecordValue)decodingHypothesis.newInstance();
			for (String fieldName : recordValue.getFieldNames()) {
				Value fieldValue = recordValue.getField(fieldName);
				// FIXME fieldValue.notPresent() always returns true !!
//				if (fieldValue.notPresent()) {
//					recordValue.setFieldOmitted(fieldName);
//				} else {
					_logger.info(String.format("Process %s - %s - %s", fieldName, fieldValue.getValueEncodingVariant(), decodingContext.toString()));
					if (!decodingContext.isEndOfBufferReached()) {
						if (fieldName != "secHeader") { // FIXME To be improved
							fieldValue = decodeProtocolLayer(fieldValue.getType(), decodingContext);
							recordValue.setField(fieldName, fieldValue);
						} else {
							recordValue.setFieldOmitted(fieldName);
						}
					} else {
						recordValue.setFieldOmitted(fieldName);
					}
//				}
			} // End of 'for' statement
		}

		_logger.exiting(this.toString(), "decodeRecordValue", recordValue.toString());
		return recordValue;
	}

	/**
	 * This methods decodes a protocol message, based on an union data structure
	 * @param decodingHypothesisThe requested type
	 * @param decodingContext The protocol message in bytes format
	 * @return An <code>UnionValue</code> instance resulting of the decoding operations
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or constructor
	 */
	private UnionValue decodeUnionValue(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering(this.toString(), "decodeUnionValue", String.format("%s - %s", decodingHypothesis.getName(), decodingContext.toString()));

		UnionValue unionValue = (UnionValue)decodingHypothesis.newInstance();
		if (decodingHypothesis.getDefiningModule().getModuleName() != decodingContext.getValueEncoding()) { // FIXME To be tested
			// Retrieve the correct codec
			TciCDProvided codec = getCodec(decodingHypothesis.getTypeEncoding());
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", decodingHypothesis.getDefiningModule().getModuleName()));
			}
			// And continue decoding operations
			byte[] newMessage = null;
			if (((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() == 0) {
				// Payload length was not forced by TTCN-3 test case, use bitpos to calculate the length of the payload
				newMessage = decodingContext.getRemaindingBytes();
			} else {
				// Payload length was forced by TTCN-3 test case, use this value
				newMessage = decodingContext.extractBytes(((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt()/*Length in bytes*/);
			}
			// Set the variant field
			unionValue.setVariant(unionValue.getVariantNames()[0], codec.decode(new TriMessageImpl(newMessage), decodingHypothesis));
		} else {
			String variantName = null;
			GeoNetworkingTypesEnum enumType; // Used when the union variant selector is an enum type
			Value decodedType = null; // Used when the union variant selector is an record type
			if (decodingHypothesis.getName() == "StationType") {
				/**
				 * See ETSI TS 102 636-4-1 - Table 1: Fields of GeoNetworking address
				 */
				// We have to decode stationType first to decide the variant to use
				// FIXME Q&D code, need to be improved: problem is that constraint is not accessible and type is Type_133 !!!!!!
				// Retrieve stationType
				BitstringValue bit = decodeBitstringValue(decodingContext, 1);
				if (bit.getBit(0) == 1) {
					variantName = "roadsideStation";
					enumType = GeoNetworkingTypesEnum.e_roadsideStation;
				} else {
					variantName = "vehicleStation";
					enumType = GeoNetworkingTypesEnum.e_vehicleStation;
				}
				decodedType = (RecordValue) unionValue.getVariant(variantName).getType().newInstance();
			} else if (decodingHypothesis.getName() == "Header") {
				/**
				 * See ETSI TS 102 636-4-1 - Table 4: Fields of the Common Header
				 */
				// We have to decode commonHeader first to decide the variant to use
				// FIXME Q&D code, need to be improved: problem is that constraint is not accessible and type is Type_133 !!!!!!
				decodingContext.set_commonHeader((RecordValue)decodeProtocolLayer(((RecordValue)unionValue.getVariant(unionValue.getVariantNames()[0])).getField(((RecordValue)unionValue.getVariant(unionValue.getVariantNames()[0])).getFieldNames()[0]).getType(), decodingContext));
				_logger.info(String.format("Header: %s", decodingContext.get_commonHeader().toString()));
				enumType = GeoNetworkingTypesEnum.valueOf(((EnumeratedValue)((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getField(((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getFieldNames()[0])).getEnum());
				switch (enumType) {
					case e_any:
						variantName = "anyHeader";
						break;
					case e_beacon:
						variantName = "beaconHeader";
						break;
					case e_geoUnicast:
						variantName = "geoUnicastHeader";
						break;
					case e_geoAnycast:
						variantName = "geoAnycastHeader";
						break;
					case e_geoBroadcast:
						variantName = "geoBroadcastHeader";
						break;
					case e_topologicallyScopedBroadcast: {
						GeoNetworkingTypesEnum subHeader = GeoNetworkingTypesEnum.valueOf(((EnumeratedValue)((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getField(((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getFieldNames()[1])).getEnum());
							if (subHeader == GeoNetworkingTypesEnum.e_singleHop) {
								variantName = "shbHeader";
							} else {
								variantName = "tsbHeader";
							}
						}
						break;
					case e_locationService: {
						GeoNetworkingTypesEnum subHeader = GeoNetworkingTypesEnum.valueOf(((EnumeratedValue)((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getField(((RecordValue)((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getVariant(((UnionValue)decodingContext.get_commonHeader().getField("headerTST")).getPresentVariantName())).getFieldNames()[1])).getEnum());
							if (subHeader == GeoNetworkingTypesEnum.e_lsRequest) {
								variantName = "lsRequestHeader";
							} else {
								variantName = "lsReplyHeader";
							}
						}
						break;
					case e_serviceAnnouncement:
						throw new RuntimeException("LibItsGeoNetworking_TypesAndValues.decodeUnionValue: Unsupported e_serviceAnnouncement");
						//break;
					case e_reserved:
						variantName = "reserved";
						break;
				} // End of 'switch' statement
				decodedType = (RecordValue) unionValue.getVariant(variantName).getType().newInstance();
				((RecordValue)decodedType).setField(((RecordValue)decodedType).getFieldNames()[0], decodingContext.get_commonHeader());
			} else if (decodingHypothesis.getName() == "HeaderTST") {
				/**
				 * See ETSI TS 102 636-4-1 - Table 6: GeoNetworking Header Types and Header Sub-Types
				 */
				// We have to decode HeaderType first to decide the variant to use
				// FIXME Q&D code, need to be improved: problem is that constraint is not accessible and type is Type_133 !!!!!!
				// Retrieve HeaderType
				//EnumeratedValue HeaderType = (EnumeratedValue)decodeProtocolLayer(unionValue.getVariant(unionValue.getVariantNames()[0]).getType());
				BitstringValue headerType = decodeBitstringValue(decodingContext, 4);
				int value1 = 0;
				for (int i = 0; i < headerType.getLength(); i++) {
					value1 = (value1 << 1) | headerType.getBit(i);
				}
				_logger.info(String.format("HeaderType: %d", value1));
				switch (value1) {
					case 0:
						variantName = "anyHdr";
						enumType = GeoNetworkingTypesEnum.e_any;
						break;
					case 1:
						variantName = "beaconHdr";
						enumType = GeoNetworkingTypesEnum.e_beacon;
						break;
					case 2:
						variantName = "geoUnicastHdr";
						enumType = GeoNetworkingTypesEnum.e_geoUnicast;
						break;
					case 3:
						variantName = "geoAnycastHdr";
						enumType = GeoNetworkingTypesEnum.e_geoAnycast;
						break;
					case 4:
						variantName = "geoBroadcastHdr";
						enumType = GeoNetworkingTypesEnum.e_geoBroadcast;
						break;
					case 5:
						variantName = "tsbHdr";
						enumType = GeoNetworkingTypesEnum.e_topologicallyScopedBroadcast;
						break;
					case 6:
						variantName = "lsHdr";
						enumType = GeoNetworkingTypesEnum.e_locationService;
						break;
					case 7:
						variantName = "saHdr";
						enumType = GeoNetworkingTypesEnum.e_serviceAnnouncement;
						break;
					default:
						variantName = "reserved";
						enumType = GeoNetworkingTypesEnum.e_reserved;
						break;
				} // End of 'switch' statement
				decodedType = (RecordValue) unionValue.getVariant(variantName).getType().newInstance();
			} else if (decodingHypothesis.getName() == "Payload") {
				/*
				 * If CommonHeader.PL field is set to 0, this means that we have to calculate the length of the payload
				 * Otherwise, the value of CommonHeader.PL field was forced by TTCN TC with a wrong value. In this case,
				 * we truncate the message buffer to the specified length and we try to decode it
				 */
				// FIXME Q&D code, need to be improved: problem is that constraint is not accessible and type is Type_133 !!!!!!
				String nextHeader = ((EnumeratedValue)decodingContext.get_commonHeader().getField("nextHeader")).getEnum();
				_logger.info(String.format("Payload: PL=%d - NH:%s", ((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt(), nextHeader));
				if (nextHeader == "e_any") {
					// Decode 'otherPayload' field
					variantName = "otherPayload";
					enumType = GeoNetworkingTypesEnum.e_any;
					int length = 0; // length in bits
					if (((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() != 0) {
						length = ((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() * 8;
					}
					decodedType = decodeOctetstringValue(decodingContext, length);
				} else if ((nextHeader == "e_btpA") || (nextHeader == "e_btpB")) {
					// Decode 'btpPacket' field
					variantName = "btpPacket";
					enumType = GeoNetworkingTypesEnum.valueOf(nextHeader);
					int length = 0; // length in bits
					if (((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() != 0) {
						length = ((IntegerValue)decodingContext.get_commonHeader().getField("plLength")).getInt() * Byte.SIZE;
					}
					if (length != 0) {
						int lengthInByte = length / Byte.SIZE;
						// Build BT packet
						ByteArrayOutputStream btpPayload = new ByteArrayOutputStream();
						try {
							btpPayload.write(0x34);
							btpPayload.write(ByteHelper.intToByteArray(lengthInByte + 3, 2));
							if (nextHeader == "e_btpA") {
								btpPayload.write(TestAdapterMessageTypeEnum.BtpAHeader.convert());
							} else {
								btpPayload.write(TestAdapterMessageTypeEnum.BtpBHeader.convert());
							}
							btpPayload.write(ByteHelper.intToByteArray(lengthInByte, 2));
							btpPayload.write(decodingContext.getNextBytes(lengthInByte));
						} catch (IOException e) {
							e.printStackTrace();
						}
						Type btpDecodingHypothesis = unionValue.getVariant(variantName).getType();
						TciCDProvided codec = getCodec(btpDecodingHypothesis.getDefiningModule().getModuleName());
						if (codec == null) {
							throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", btpDecodingHypothesis.getDefiningModule().getModuleName()));
						}
						// And continue decoding operations
						decodedType= (RecordValue) codec.decode(new TriMessageImpl(btpPayload.toByteArray()), btpDecodingHypothesis);
						unionValue.setVariant(variantName, decodedType);
						// No more field, terminate decoding here
						_logger.exiting(this.toString(), "decodeUnionValue", unionValue.toString());
						return unionValue;
					} else {
						throw new RuntimeException(String.format("Next header is '%s', payload is expected", nextHeader));
					}
				} else if (nextHeader == "e_ipv6") {
					// Decode 'ipv6Packet' field
					variantName = "ipv6Packet";
					enumType = GeoNetworkingTypesEnum.e_ipv6;
					decodedType = decodeProtocolLayer(unionValue.getVariant(variantName).getType(), decodingContext);
				} else {
					throw new RuntimeException(String.format("Unknown 'next header' value: %s", nextHeader));
				}
				_logger.info(String.format("Payload: %s", decodedType.toString()));
			} else {
				throw new RuntimeException(String.format("Unimplemented union: %s", decodingHypothesis.getName()));
			}

			// Set the selector field
			if (decodedType.getType().getTypeClass() == TciTypeClass.OCTETSTRING) {
				unionValue.setVariant(variantName, decodedType);
			} else if (decodedType.getType().getTypeClass() == TciTypeClass.RECORD) {
				// Instantiate the variant
//				_logger.info(String.format("Instantiate variant %s", variantName));
//				RecordValue recordValue = (RecordValue) unionValue.getVariant(variantName).getType().newInstance();
				RecordValue recordValue = (RecordValue)decodedType;

				switch (recordValue.getField(recordValue.getFieldNames()[0]).getType().getTypeClass()) {
					case TciTypeClass.ENUMERATED:
						((EnumeratedValue)recordValue.getField(recordValue.getFieldNames()[0])).setEnum(enumType.name());
						break;
					case TciTypeClass.RECORD:
						// Nothing to do, commHeader was previously assigned
						break;
//					case TciTypeClass.UNION:
//						selector = decodedType;
//						break;
					default:
						throw new RuntimeException(String.format("Unimplemented type class: %d - %s", decodedType.getType().getTypeClass(), decodedType.getType().getName()));
				}

				// Set the other fields if needed
				if (!decodingContext.isEndOfBufferReached()) {
					String[] recordFields = recordValue.getFieldNames();
					for (int i = 1; i < recordFields.length; i += 1) {
						_logger.info(String.format("Process field: %s", recordValue.getFieldNames()[i]));
						Value fieldValue = decodeProtocolLayer(recordValue.getField(recordFields[i]).getType(), decodingContext);
						if (fieldValue != null) {
							_logger.info(fieldValue.toString());
							recordValue.setField(recordValue.getFieldNames()[i], fieldValue);
						} else {
							recordValue.setFieldOmitted(recordValue.getFieldNames()[i]);
						}
					}
				}
				// Set the variant field
				unionValue.setVariant(variantName, recordValue);
			}
		}

		_logger.exiting(this.toString(), "decodeUnionValue", unionValue.toString());
		return unionValue;
	} // End of method decodeUnionValue
	
	/**
	 * This methods decodes a protocol message, based on an enumerated data structure
	 * @param decodingHypothesisThe requested type
	 * @param decodingContext The protocol message in bytes format
	 * @return An <code>EnumeratedValue</code> instance resulting of the decoding operations
	 * @throws InstantiationException Thrown when an application tries to create an instance of a class using the newInstance method in class Class, but the specified class object cannot be instantiated
	 * @throws IllegalAccessException An IllegalAccessException is thrown when an application tries to reflectively create an instance (other than an array), set or get a field, or invoke a method, but the currently executing method does not have access to the definition of the specified class, field, method or constructor
	 */
	private EnumeratedValue decodeEnumeratedValue(final Type decodingHypothesis, DecodingContext decodingContext) {
		_logger.entering(this.toString(), "decodeEnumeratedValue", String.format("%s - %s", decodingHypothesis.getName(), decodingContext.toString()));

		EnumeratedValue enumValue = (EnumeratedValue)decodingHypothesis.newInstance();

		_logger.info(String.format("Encoding variant: %s", enumValue.getValueEncodingVariant()));
		if (enumValue.getValueEncodingVariant() != null) {
			Matcher matcher = ItsCodecImpl.PatternVariantEncoding.matcher(enumValue.getValueEncodingVariant());
			if (matcher.find()) {
				BitstringValue bitValue = decodeBitstringValue(decodingContext, Integer.parseInt(matcher.group(3)));
				int value = 0;
				for (int i = 0; i < bitValue.getLength(); i++) {
					value = (value << 1) | bitValue.getBit(i);
				}
				if (enumValue.getType().getName() == "TypeOfAddress") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 1: Fields of the GeoNetworking address
					 */
					enumValue.setEnum((value == 0) ? GeoNetworkingTypesEnum.e_initial.name() : GeoNetworkingTypesEnum.e_manual.name());
				} else if (enumValue.getType().getName() == "StationSubType") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 1: Fields of the GeoNetworking address
					 */
					enumValue.setEnum((value == 0) ? GeoNetworkingTypesEnum.e_public.name() : GeoNetworkingTypesEnum.e_private.name());
				} else if (enumValue.getType().getName() == "StationTypeIdentifier") { // FIXME Why constraint is not accessble, Type_133
					/**
					 * See ETSI TS 102 636-4-1 - Table 1: Fields of the GeoNetworking address
					 */
					enumValue.setEnum((value == 0) ? GeoNetworkingTypesEnum.e_vehicleStation.name() : GeoNetworkingTypesEnum.e_roadsideStation.name());
				} else if (enumValue.getType().getName() == "VsKind") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 1: Fields of the GeoNetworking address
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_bike.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_motorbike.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_car.name());
							break;
						case 0x03:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_truck.name());
							break;
						case 0x04:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_bus.name());
							break;
						default:
							throw new RuntimeException(String.format("VsKind: 0x%02x enum value unknown", value));
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "RsKind") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 1: Fields of the GeoNetworking address
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_trafficLight.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_ordinary.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "NextHeader") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 4: Next Header (NH) field in GeoNetworking Common Header
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_any.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_btpA.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_btpB.name());
							break;
						case 0x03:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_ipv6.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "Reliability") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 7: Encoding of the Traffic Class (TC) sub-field Reliability
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_veryHigh.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_high.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_medium.name());
							break;
						case 0x03:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_low.name());
							break;
						default:
							throw new RuntimeException(String.format("Reliability: 0x%02x enum value unknown", value));
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "Latency") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 8: Encoding of the TC sub-field Latency
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_veryLow.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_low.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_medium.name());
							break;
						case 0x03:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_high.name());
							break;
						default:
							throw new RuntimeException(String.format("Latency: 0x%02x enum value unknown", value));
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "LtBase") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 9: Encoding of LT sub-field LT Base
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_50ms.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_1s.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_10s.name());
							break;
						case 0x03:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_100s.name());
							break;
						default:
							throw new RuntimeException(String.format("LtBase: 0x%02x enum value unknown", value));
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "HeaderSubTypeLs") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 9: Encoding of LT sub-field LT Base
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_lsRequest.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_lsReply.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "HeaderSubTypeGeoBroadcast") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 6: GeoNetworking Header Types and Header Sub-Types
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoBroadcastCircle.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoBroadcastRect.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoBroadcastElip.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "HeaderSubTypeGeoAnycast") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 6: GeoNetworking Header Types and Header Sub-Types
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoAnycastCircle.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoAnycastRect.name());
							break;
						case 0x02:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_geoAnycastElip.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else if (enumValue.getType().getName() == "HeaderSubTypeTSB") {
					/**
					 * See ETSI TS 102 636-4-1 - Table 6: GeoNetworking Header Types and Header Sub-Types
					 */
					switch (value) {
						case 0x00:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_singleHop.name());
							break;
						case 0x01:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_multiHop.name());
							break;
						default:
							enumValue.setEnum(GeoNetworkingTypesEnum.e_reserved.name());
							break;
					} // End of 'switch' statement
				} else {
					throw new RuntimeException(String.format("Enumearated '%s' not implemented yet", decodingHypothesis.getName()));
				}
			} else {
				throw new RuntimeException("LibItsGeoNetworking_TypesAndValues.decodeEnumeratedValue: failed to extract Encoding Variant value");
			}
		} else {
			throw new RuntimeException("LibItsGeoNetworking_TypesAndValues.decodeEnumeratedValue: Encoding Variant value is null");
		}
		
		return enumValue;
	} // End of method decodeEnumeratedValue

} // End of class LibItsGeoNetworking_TypesAndValues
