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

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

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.IntegerValue;
import org.etsi.ttcn.tci.OctetstringValue;
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 LibItsIpv6OverGeoNetworking_TypesAndValues TTCN-3 module (ITS message primitives)
 * 
 * See 'ETSI TS 102 870-3 Clause 4.4.1 Primitives of the btpPort' for message primitives on BTP port
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsBtp_TypesAndValuesCodec extends ItsCodecImpl {

	/**
	 * Type of the BTP header
	 * 
	 * See ETSI TS 102 636-5-1 - Clause 7
	 */
	private BTPTypesEnum _btpType;

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

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

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

		if (rv == null) {
			return;
		}

		if (
				(rv.getType().getName() == TestAdapterMessageTypeEnum.UtBtpInitialize.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.BtpPacket.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.BtpAHeader.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.BtpBHeader.name()) || 
				(rv.getType().getName() == TestAdapterMessageTypeEnum.BtpPayload.name())) {
			// Non protocol message
			encodeValueWithTLV(encodingContext, rv);
		} else {
			// Protocol message
			super.encodeRecordValue(encodingContext, rv);
		}

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

	/**
	 * This method is called to encode UtBtpEvent using TLV format
	 * @param encodingContext The context to encode the TTCN-3 message.
	 * @param value The <code>Value</code> from getting subfields.
	 * 
	 * See ETSI TS 102 636-5-1 V1.1.1 - Figure 3: BTP packet structure
	 */
	private void encodeValueWithTLV(EncodingContext encodingContext, final Value value) {
		_logger.entering("LibItsBtp_TypesAndValuesCodec", "encodeValueWithTLV", String.format("%s", value.getType().getName()));

		// Sanity check
		if (value == null) {
			return;
		}

		try {
			encodingContext.setByte(TestAdapterMessageTypeEnum.convert(value.getType().getName()));
		} catch (IOException e) {
			e.printStackTrace();
		} // Type
		if (value.notPresent()) {
			try { // Length is 0
				encodingContext.setByte((byte) 0x00);
				encodingContext.setByte((byte) 0x00);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			EncodingContext out_rv = new EncodingContext();
			switch (value.getType().getTypeClass()) {
				case TciTypeClass.RECORD:
					super.encodeRecordValue(out_rv, (RecordValue) value);
					break;
				case TciTypeClass.OCTETSTRING:
					super.encodeOctetstringValue(out_rv, (OctetstringValue) value);
					break;
				default:
					throw new RuntimeException(String.format("Not implemented type %s - %d", value.getType().getName(), value.getType().getTypeClass()));
			}
			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();
				}
			}
		}
		
//		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsBtp_TypesAndValuesCodec.encodeValueWithTLV: ", encodingContext.toByteArray());
	} // End of method encodeValueWithTLV

	/**
	 * 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("LibItsBtp_TypesAndValuesCodec", "encodeUnionValue", String.format("%s - %s", uv.getType().getName(), (uv.notPresent() == true) ? "not present" : "present"));

		if (uv == null) {
			return;
		}

		if (
				(uv.getType().getName() == "UtBtpEvent")) {
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(uv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			EncodingContext out_uv = new EncodingContext();
			if (
					(uv.getPresentVariantName() == "btpA") || 
					(uv.getPresentVariantName() == "btpB")) {
				RecordValue rv = (RecordValue) uv.getVariant(uv.getPresentVariantName());
				try {
					out_uv.setByte(TestAdapterMessageTypeEnum.valueOf(rv.getType().getName()).convert());
				} catch (IOException e) {
					e.printStackTrace();
				} // Type
				EncodingContext out_uv1 = new EncodingContext();
				for (String fieldName : rv.getFieldNames()) {
					try {
						out_uv1.setByte(TestAdapterMessageTypeEnum.valueOf(fieldName).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_uv2 = new EncodingContext();
					super.encodeIntegerValue(out_uv2, (IntegerValue) rv.getField(fieldName)); // Value
					try {
						out_uv1.setBytes((byte[])ByteHelper.intToByteArray(out_uv2.size(), LengthFieldSize));
					} catch (IOException e) {
						e.printStackTrace();
					} // Length
					if (out_uv2.size() != 0) { // Value
						try {
							out_uv1.setBytes(out_uv2.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} // End of 'for' statement
				try {
					out_uv.setBytes((byte[])ByteHelper.intToByteArray(out_uv1.size(), LengthFieldSize));
				} catch (IOException e) {
					e.printStackTrace();
				} // Length
				if (out_uv1.size() != 0) { // Value
					try {
						out_uv.setBytes(out_uv1.toByteArray());
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			} else if (uv.getPresentVariantName() == "payload") {
				UnionValue tmp = (UnionValue)uv.getVariant(uv.getPresentVariantName());
				if (tmp.getPresentVariantName() == "otherPayload") {
					OctetstringValue ov = (OctetstringValue) tmp.getVariant(tmp.getPresentVariantName());
					try {
						out_uv.setByte(TestAdapterMessageTypeEnum.valueOf(tmp.getType().getName()).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_uv1 = new EncodingContext();
					super.encodeOctetstringValue(out_uv1, ov);
					try {
						out_uv.setBytes((byte[])ByteHelper.intToByteArray(out_uv1.size(), LengthFieldSize));
					} catch (IOException e) {
						e.printStackTrace();
					} // Length
					if (out_uv1.size() != 0) { // Value
						try {
							out_uv.setBytes(out_uv1.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} else {
					throw new RuntimeException(String.format("Not implemented type %s", tmp.getPresentVariantName()));
				}
			} else {
				encodeValueWithTLV(out_uv, uv.getVariant(uv.getPresentVariantName()));
			}
			try {
				encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_uv.size(), LengthFieldSize));
			} catch (IOException e) {
				e.printStackTrace();
			} // Length
			if (out_uv.size() != 0) { // Value
				try {
					encodingContext.setBytes(out_uv.toByteArray());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} else {
			super.encodeUnionValue(encodingContext, uv);
		}

//		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsBtp_TypesAndValuesCodec.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("LibItsBtp_TypesAndValuesCodec", "decodeTLVLayer", decodingHypothesis.getName());

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

		return value;
	}

	/** 
	 * This method decodes the current TLV sequence, based on RecordValue
	 * @param recordValue A record 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 RecordValue
	 */
	private RecordValue decodeTLVLayer(RecordValue recordValue, DecodingContext decodingContext) throws InstantiationException, IllegalAccessException {
		_logger.entering("LibItsBtp_TypesAndValuesCodec", "decodeTLVLayer (2)", recordValue.getType().getName());

		// Extract type
		TestAdapterMessageTypeEnum type = TestAdapterMessageTypeEnum.valueOf(decodingContext.getNextByte());
		// And process it
		switch (type) {
			case BtpPacket:
				//No break;
				decodingContext.getNextBytes(2); // Get Length (2 bytes)
				recordValue = decodeTLVLayer(recordValue, decodingContext);
				break;
			case BtpAHeader:
				//No break;
			case BtpBHeader:
				decodingContext.getNextBytes(2); // Get Length (2 bytes)
				// Protocol message
				if (type == TestAdapterMessageTypeEnum.BtpAHeader) {
					_btpType = BTPTypesEnum.BTP_A;
				} else {
					_btpType = BTPTypesEnum.BTP_B;
				}
				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("LibItsBtp_TypesAndValuesCodec", "decodeProtocolLayer", decodingHypothesis.getName());

		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.INTEGER:
				value = decodeIntegerValue(decodingHypothesis, decodingContext);
				break;
			case TciTypeClass.OCTETSTRING:
				int length = decodingContext.getCountOfRemaindingBits();
				value = decodeOctetstringValue(decodingContext, length, 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 encodedMsg 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("LibItsBtp_TypesAndValuesCodec", "decodeRecordValue", decodingHypothesis.getName());
		
		RecordValue 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()) {
					fieldValue = decodeProtocolLayer(fieldValue.getType(), decodingContext);
					recordValue.setField(fieldName, fieldValue);
				} else {
					recordValue.setFieldOmitted(fieldName);
				}
//			}
		}

		_logger.exiting("LibItsBtp_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 encodedMsg 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("LibItsBtp_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() == "BtpHeader") {
			/**
			 * See ETSI TS 102 636-5-1 V1.1.1 - Clause 7.1 Overview
			 * See ETSI TS 102 636-5-1 V1.1.1 - Table 2: Fields of BTP-A header
			 * See ETSI TS 102 636-5-1 V1.1.1 - Table 3: Fields of BTP-B header
			 */
			if (_btpType == BTPTypesEnum.BTP_A) {
				variantName = "btpAHeader";
			} else if (_btpType == BTPTypesEnum.BTP_B) {
				variantName = "btpBHeader";
			} else {
				throw new RuntimeException("LibItsBtp_TypesAndValuesCodec.decodeUnionValue: BTP header (type A or B) not set");
			}
			decodedType = decodeProtocolLayer(unionValue.getVariant(variantName).getType(), decodingContext);
		} else if (decodingHypothesis.getName() == "BtpPayload") {
			TriMessage message = new TriMessageImpl(decodingContext.getRemaindingBytes());
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("BtpPayload dump: ", message.getEncodedMessage());
			// Check with CamPdu
			variantName = "camPacket";
			TciCDProvided codec = getCodec("LibItsCam_asn1");
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", decodingHypothesis.getDefiningModule().getModuleName()));
			}
			if ((decodedType = codec.decode(message , unionValue.getVariant(variantName).getType())) == null) {
				// Check with DenmPdu
				variantName = "denmPacket";
				if ((codec = getCodec("LibItsDenm_asn1")) == null) {
					throw new RuntimeException(String.format("Could not retrieve codec for decoding name %s", decodingHypothesis.getDefiningModule().getModuleName()));
				}
				if ((decodedType = codec.decode(message, unionValue.getVariant(variantName).getType())) == null) {
					// It is an octetstring
					variantName = "otherPayload";
					decodedType = decodeOctetstringValue(new DecodingContext(message.getEncodedMessage()), message.getEncodedMessage().length * Byte.SIZE);
				}
			}
		} else {
			throw new RuntimeException(String.format("LibItsBtp_TypesAndValuesCodec.decodeUnionValue: Unsupported variant %s - %d", decodingHypothesis.getName(), decodingHypothesis.getTypeClass()));
		}
		// Set the variant field
		unionValue.setVariant(variantName, decodedType);

		_logger.exiting("LibItsBtp_TypesAndValuesCodec", "decodeUnionValue", unionValue.toString());
		return unionValue;
	} // End of method decodeUnionValue

} // End of class LibItsBtp_TypesAndValuesCodec
