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

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;

import org.etsi.codec.DecodingContext;
import org.etsi.codec.EncodingContext;
import org.etsi.codec.TciCDWrapperFactory;
import org.etsi.common.ByteHelper;
import org.etsi.its.codec.ItsCodecImpl;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.its.extfunc.IItsExternalFunctionsProvider;
import org.etsi.its.extfunc.ItsExternalFunctionsProvider;
import org.etsi.ttcn.tci.IntegerValue;
import org.etsi.ttcn.tci.OctetstringValue;
import org.etsi.ttcn.tci.RecordOfValue;
import org.etsi.ttcn.tci.RecordValue;
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 LibItsIpv6OverGeoNetworking_TypesAndValues TTCN-3 module (ITS message primitives)
 * 
 * See 'ETSI TS 102 859-3 Clause 4.4.1 Primitives of the ipv6OverGeoNetworkingPort' for message primitives on ipv6OverGeoNetworkingPort port
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsIpv6OverGeoNetworking_TypesAndValuesCodec extends ItsCodecImpl {

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

		_logger.entering("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "Constructor", String.format("version: %s", Version));
	} // 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "encode", template.toString());

		try {
			TriMessage encoded = super.encode(template);
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec.encode: ", 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec.decode: ", encodedMsg);
			DecodingContext decodingContext = new DecodingContext(encodedMsg, decodingHypothesis.getTypeEncoding());
			
			// Check which type class the decoding hypothesis is of
			Value value = decodeTLVLayer(decodingHypothesis, decodingContext);
			_logger.exiting("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "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) { // TODO To be removed
		_logger.entering(this.toString(), "encodeRecordValue", String.format("%s - %s", rv.getType().getName(), (rv.notPresent() == true) ? "not present" : "present"));

		if (rv == null) {
			return;
		}
		
		// Protocol message
		super.encodeRecordValue(encodingContext, rv);
	} 
	
	/**
	 * 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "encodeUnionValue", String.format("%s - %s", uv.getType().getName(), (uv.notPresent() == true) ? "not present" : "present"));

		if (uv == null) {
			return;
		}

		if ((uv.getType().getName() == "AcGn6Primitive") || (uv.getType().getName() == "AcGn6Response")) {
			try { // Type
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(uv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			}
			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() == "Ipv6Payload")) {
			EncodingContext out_uv = new EncodingContext();
			super.encodeUnionValue(out_uv, uv);
			if (out_uv.size() != 0) {
				try {
					if (uv.getPresentVariantName() == "routerAdvMsg") { // FIXME Use TestEnum...
						// Set IPv6 source address value
						OctetstringValue ipv6SrcAddress = TciCDWrapperFactory.getTciCDInstance().getOctetstring();
						ipv6SrcAddress.setLength(16);
						byte[] buffer = encodingContext.getBytesAtMarkForIPv6SrcAddress(16);
						for (int i = 0; i < 16; i++) {
							ipv6SrcAddress.setOctet(i, buffer[i]);
						}
						// Set IPv6 destination address value
						OctetstringValue ipv6DstAddress = TciCDWrapperFactory.getTciCDInstance().getOctetstring();
						ipv6DstAddress.setLength(16);
						buffer = encodingContext.getBytesAtMarkForIPv6DstAddress(16);
						for (int i = 0; i < 16; i++) {
							ipv6DstAddress.setOctet(i, buffer[i]);
						}
						// Set payload length value
						IntegerValue payloadLength = TciCDWrapperFactory.getTciCDInstance().getInteger();
						payloadLength.setInt(out_uv.size());
						// Set payload value
						OctetstringValue payload = TciCDWrapperFactory.getTciCDInstance().getOctetstring();
						payload.setLength(payloadLength.getInt());
						buffer = out_uv.toByteArray();
						for (int i = 0; i < payload.getLength(); i++) {
							payload.setOctet(i, buffer[i]);
						}
						// Set NextHeader value
						IntegerValue nextHdr = TciCDWrapperFactory.getTciCDInstance().getInteger();
						nextHdr.setInt(ByteHelper.byteArrayToInt(encodingContext.getBytesAtMarkForNextHeader(1)));
						// Calculate IPv6 checksum
						IItsExternalFunctionsProvider externalFuntions = new ItsExternalFunctionsProvider();
						OctetstringValue checksum = externalFuntions.fx_computeIPv6CheckSum(
								ipv6SrcAddress,
								ipv6DstAddress, 
								payloadLength, 
								payload, 
								nextHdr 
						);
						buffer = new byte[] { (byte) checksum.getOctet(0), (byte) checksum.getOctet(1) };
						out_uv.setBytesAtMarkForIPv6ChecksumCalculation(buffer);
						encodingContext.setBytes(out_uv.toByteArray());
						encodingContext.setBytesAtMark(ByteHelper.intToByteArray(out_uv.size(), 2)); // 2 byte length
					} else if (uv.getPresentVariantName() == "octetstringMsg") { // FIXME Use TestEnum...
						encodingContext.setBytes(out_uv.toByteArray());
						encodingContext.setBytesAtMark(ByteHelper.intToByteArray(out_uv.size(), 2)); // 2 byte length
					} else {
						throw new RuntimeException(String.format("Not implemented field %s - %s", uv.getPresentVariantName(), uv.getVariant(uv.getPresentVariantName()).getType()));
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} else {
			super.encodeUnionValue(encodingContext, uv);
		}

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

	/**
	 * This method checks the present subfield-type to call the representing method
	 * 
	 * @param encodingContext The context to encode the TTCN-3 message
	 * @param rov The <code>RecordofValue</code> from getting subfields.
	 */
	@Override
	public void encodeRecordofValue(EncodingContext encodingContext, final RecordOfValue rov) {
		_logger.entering("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "encodeRecordofValue", rov.getType().getName());

		// Set type
		if ((rov.getType().getName() == "AcGn6InterfaceInfoList")/* || (rov.getType().getName() == "Ipv6AddressList")*/) {
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(rov.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			EncodingContext out_rv = new EncodingContext();
			// Write number of items
			try {
				out_rv.setBytes(ByteHelper.intToByteArray(rov.getLength(), Integer.SIZE / Byte.SIZE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			// Write items
			super.encodeRecordofValue(out_rv, rov);
			try { // Length
				encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_rv.size(), LengthFieldSize));
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (out_rv.size() != 0) { // Value
				try {
					encodingContext.setBytes(out_rv.toByteArray());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} else if (rov.getType().getName() == "Ipv6AddressList") {
			// Write number of items
			try {
				encodingContext.setBytes(ByteHelper.intToByteArray(rov.getLength(), Integer.SIZE / Byte.SIZE));
			} catch (IOException e) {
				e.printStackTrace();
			}
			// Write items
			super.encodeRecordofValue(encodingContext, rov);
		} else if (rov.getType().getName() == "PrefixInfoList") {
			// Protocol message
			super.encodeRecordofValue(encodingContext, rov);
		} else {
			throw new RuntimeException(String.format("Not implemented type class %s - %s", rov.getType().getName(), rov.getType().getTypeClass()));
		}

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

	/** 
	 * 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "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.RECORD_OF:
				// Create a new instance
				final RecordOfValue recordOfValue = (RecordOfValue)decodingHypothesis.newInstance();
				// And fill it
				value = decodeTLVLayer(recordOfValue, decodingContext);
				break;
//			case TciTypeClass.INTEGER:
//				value = decodeIntegerValue(decodingHypothesis, encodedMsg);
//				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 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeTLVLayer (2)", recordValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.peekNextByte());
		_logger.info(String.format("(1): type: %s", type));
		if ((type == null) || (type.name() != recordValue.getType().getName())) {
			// It is not a TLV format, process it as a protocol format
			return (RecordValue)decodeProtocolLayer(recordValue.getType(), decodingContext);
		}
		// And process it
		switch (type) {
			case AcGn6InterfaceInfoList:
				decodingContext.getNextByte(); // Skip Type
				decodingContext.getNextBytes(2); // Skip Length (2 bytes)
				// Protocol message
				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 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeTLVLayer (1)", unionValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.peekNextByte());
		_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
		decodingContext.getNextByte(); // Skip Type
		decodingContext.getNextBytes(2); // Skip Length (2 bytes)
		switch (type) {
			case AcGn6Response:
			    // only one variant.
			    type = TestAdapterMessageTypeEnum.AcGn6InterfaceInfoList;
				for (String variantName : unionValue.getVariantNames()) {
					if (unionValue.getVariant(variantName).getType().getName() == type.name()) {
						Value value = decodeTLVLayer(unionValue.getVariant(variantName).getType(), decodingContext);
						unionValue.setVariant(variantName, value);
						return unionValue;
					}
				} // End of 'for' statement
				throw new RuntimeException(String.format("Variant not found for '%s'", unionValue.getType().getName()));
			case AcGn6Primitive:
				unionValue.setVariant(unionValue.getVariantNames()[0], super.decodeIntegerValue(decodingContext));
				break;
		} // End of 'switch' statement

		return unionValue;
	} // End of method decodeTLVLayer

	/** 
	 * This method decodes the current TLV sequence, based on recordOf
	 * @param unionValue An union 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 UnionValue
	 */
	private RecordOfValue decodeTLVLayer(RecordOfValue recordOfValue, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeTLVLayer (3)", recordOfValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.peekNextByte());
		_logger.info(String.format("decodeTLVLayer (3): type: %s", type));
		if ((type == null) || (type.name() != recordOfValue.getType().getName())) {
			// It is not a TLV format, process it as a protocol format
			return (RecordOfValue)decodeProtocolLayer(recordOfValue.getType(), decodingContext);
		}
		// And process it
		decodingContext.getNextByte(); // Skip Type
		decodingContext.getNextBytes(2); // Skip Length (2 bytes)
		switch (type) {
			case AcGn6InterfaceInfoList:
				// Protocol message
				recordOfValue = (RecordOfValue)decodeProtocolLayer(recordOfValue.getType(), decodingContext);
				break;
		}

		return recordOfValue;
	} // 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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "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.SET:
				// No break;
			case TciTypeClass.RECORD:
				value = decodeRecordValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.SET_OF:
				// No break;
			case TciTypeClass.RECORD_OF:
				value = decodeRecordOfValue(decodingHypothesis, decodingContext);
				break;
//			case TciTypeClass.ENUMERATED:
//				value = decodeEnumeratedValue(decodingHypothesis, encodedMsg);
//				break;
			case TciTypeClass.INTEGER:
				value = decodeIntegerValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.ANYTYPE:
				//No break;
			case TciTypeClass.OCTETSTRING:
				value = decodeOctetstringValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.CHARSTRING:
				value = decodeCharstringValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.BITSTRING:
				value = decodeBitstringValue(decodingHypothesis, decodingContext);
				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 complete encoded message
	 * @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("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeRecordValue", decodingHypothesis.getName());
		
		RecordValue recordValue = (RecordValue)decodingHypothesis.newInstance();

		if (decodingHypothesis.getName() == "RtAdvOptions") { // FIXME Use Type..
			for (String fieldName : recordValue.getFieldNames()) {
				if (decodingContext.isEndOfBufferReached()) {
					recordValue.setFieldOmitted(fieldName);
					continue;
				}
				// FIXME fieldValue.notPresent() always returns true !!
	//			if (fieldValue.notPresent()) {
	//				recordValue.setFieldOmitted(fieldName);
	//			} else {
					// The next byte indicates the expected type
					String expectedFieldName = null;
					switch (decodingContext.peekNextByte()) {
						case (byte)0x01 : // Need to decode SrcLinkLayerAddress
							expectedFieldName = "srcLinkLayerAddr";
							break;
						case (byte)0x03 : // Need to decode PrefixInfoList
							expectedFieldName = "prefixInfoList";
							break;
						default : // // Need to decode octetstring
							expectedFieldName = "otherOption";
							break;
					} // End of 'switch' statement
					_logger.info(String.format("Process %s - expected:%s", fieldName, expectedFieldName));
					if (fieldName.equals(expectedFieldName)) {
						Value fieldValue = recordValue.getField(fieldName);
						_logger.info(String.format("Process %s - %s - %s", fieldName, fieldValue.getValueEncodingVariant(), decodingContext.toString()));
						if (!decodingContext.isEndOfBufferReached()) {
							fieldValue = decodeProtocolLayer(fieldValue.getType(), decodingContext);
							recordValue.setField(fieldName, fieldValue);
						} else {
							recordValue.setFieldOmitted(fieldName);
						}
					} else {
						recordValue.setFieldOmitted(fieldName);
					}
			} // End of 'for' statement
		} else {
			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 != "extHdrList") { // FIXME Work-around for fieldValue.notPresent() problem
							fieldValue = decodeProtocolLayer(fieldValue.getType(), decodingContext);
							if (fieldName == "payloadLength") {
								decodingContext.set_payloadLength((IntegerValue) fieldValue);
							} else if (fieldName == "nextHeader") {
								decodingContext.set_nextHeader((IntegerValue) fieldValue);
							}
							recordValue.setField(fieldName, fieldValue);
						} else {
							recordValue.setFieldOmitted(fieldName);
						}
					} else {
						recordValue.setFieldOmitted(fieldName);
					}
	//			}
			}
		}

		_logger.exiting("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeRecordValue", recordValue.toString());
		return recordValue;
	} // End of method decodeRecordValue

	/**
	 * 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
	 * 
	 * @see "http://www.tcpipguide.com/free/t_ICMPv6RouterAdvertisementandRouterSolicitationMess-2.htm"
	 */
	private UnionValue decodeUnionValue(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeUnionValue", String.format("%s - %s", decodingHypothesis.getName(), decodingContext.toString()));

		UnionValue unionValue = (UnionValue)decodingHypothesis.newInstance();
		String variantName = null;
		Value decodedType = null; // Used when the union variant selector is an record type
		if (decodingHypothesis.getName() == "Ipv6Payload") {
		    boolean extHeaderPresent;
		    do {
		        extHeaderPresent = false;
		        if(decodingContext.get_nextHeader().getInt() == 0) { // HBH
		            IntegerValue nh = decodingContext.get_nextHeader();
		            nh.setInt(decodingContext.getNextByte());
		            decodingContext.set_nextHeader(nh);
		            int len = ((int)decodingContext.getNextByte() + 1) * 8 - 2;
		            decodingContext.getNextBytes(len); // skip option
		            extHeaderPresent = true;
		        }		        
		    }
		    while(extHeaderPresent); 
			switch ((byte)decodingContext.get_nextHeader().getInt()) { 
			    case 0x00: // HBH
                
			    case 0x3a: // ICMPv6 payload
			        // Check if Router Adv.
			        if(decodingContext.peekNextByte() == (byte)0x86) {
                        variantName = "routerAdvMsg"; 
                        decodedType = decodeProtocolLayer(unionValue.getVariant(variantName).getType(), decodingContext);
                        break;
			        }
			        // otherwise go on, and decode as octetstring
				case 0x06: // TCP
				case 0x11: // UDP
					variantName = "octetstringMsg";
					if (decodingContext.get_payloadLength() != null) {
						int length = 0;
						if (decodingContext.get_payloadLength().getInt() == 0) {
							// Payload length was not forced by TTCN-3 test case, use _bitpos to calculate the length of the payload
							length = decodingContext.getCountOfRemaindingBits(); // Length in bits
						} else {
							// Payload length was forced by TTCN-3 test case, use this value
							length = decodingContext.get_payloadLength().getInt() * Byte.SIZE; // Length in bits
						}
						decodedType = decodeOctetstringValue(decodingContext, length);
					} else {
						throw new RuntimeException("payloadLength field was not decoded properly");
					}
					break;
				default:
					throw new RuntimeException(String.format("Unsupported IPv6 next header value: 0x%02x", (byte)decodingContext.get_nextHeader().getInt()));
			} // End of 'switch' statement
		}
		// Set the variant field
		unionValue.setVariant(variantName, decodedType);
	
		_logger.exiting("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeUnionValue", unionValue.toString());
		return unionValue;
	} // End of method decodeUnionValue

	/**
	 * This methods decodes a protocol message, based on a 'record of' data structure
	 * @param decodingHypothesis The requested type
	 * @param decodingContext The protocol message in bytes format
	 * @return A <code>RecordOfValue</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 RecordOfValue decodeRecordOfValue(final Type decodingHypothesis, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeRecordOfValue", decodingHypothesis.getName());
		
		RecordOfValue recordofValue = (RecordOfValue)decodingHypothesis.newInstance();
		if (decodingHypothesis.getName() == "PrefixInfoList") { // FIXME Use TypeEnum...
			// Protocol message
			while (!decodingContext.isEndOfBufferReached()) { // Decode message
				Value fieldValue = decodeProtocolLayer(recordofValue.getElementType(), decodingContext);
				recordofValue.appendField(fieldValue);
			}
		} else {
			// Read # of items
			recordofValue.setLength(ByteHelper.byteArrayToInt(decodingContext.getNextBytes(Integer.SIZE / Byte.SIZE)));
			// Decode items
			for (int i = 0, n = recordofValue.getLength(); i < n; i++) {
				Value fieldValue = decodeProtocolLayer(recordofValue.getField(i).getType(), decodingContext);
				recordofValue.setField(i, fieldValue);
			} // End of 'for' statement
		}
		_logger.exiting("LibItsIpv6OverGeoNetworking_TypesAndValuesCodec", "decodeRecordOfValue", recordofValue.toString());
		return recordofValue;
	} // End of method decodeUnionValue

} // End of class LibItsIpv6OverGeoNetworking_TypesAndValuesCodec
