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

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

import org.etsi.codec.DecodingContext;
import org.etsi.codec.EncodingContext;
import org.etsi.common.ByteHelper;
import org.etsi.its.codec.ItsCodecImpl;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.ttcn.tci.RecordValue;
import org.etsi.ttcn.tci.TciCDProvided;
import org.etsi.ttcn.tci.TciCDRequired;
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 LibItsCommon_TypesAndValues TTCN-3 module (Upper Tester messages)
 * 
 * Note that it requires closely linked to LibItsCommon_TypesAndValuesCodec TTCN-3 codec
 * 
 * See 'ETSI TS 102 868-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for CAM
 * See 'ETSI TS 102 870-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for BTP
 * See 'ETSI TS 102 869-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for DENM
 * See 'ETSI TS 102 871-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for GeoNetworking
 * See 'ETSI TS 102 859-3 Clause 4.4.3 Primitives of the utPort'for UpperTester primitives for IPv6 GeoNetworking
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsCommon_TypesAndValuesCodec extends ItsCodecImpl implements TciCDProvided {

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

		_logger.entering("LibItsCommon_TypesAndValuesCodec (1)", "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("LibItsCommon_TypesAndValuesCodec", "encode", template.toString());

		try {
			TriMessage encoded = super.encode(template);
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsCommon_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 message 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 message, final Type decodingHypothesis) {
		_logger.entering("LibItsCommon_TypesAndValuesCodec", "decode", decodingHypothesis.getName());

		// Initialise the decoder
		byte[] encodedMsg = message.getEncodedMessage();
		if (encodedMsg.length == 0) {
			_logger.severe("encodedMsg is empty");
			return null;
		}
		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsCommon_TypesAndValuesCodec.decode: ", encodedMsg);

		try {
			byte[] buffer = message.getEncodedMessage();
			Value value = null;
			if (
					(buffer.length != 4) || // 4 bytes TLV
					(buffer[0] != TestAdapterMessageTypeEnum.convert("UtResult")/*Type*/) || 
					((buffer[1] != 0x00) || (buffer[2] != 0x01))) { /* Length == 1*/
				value = super.decodeBooleanValue(new DecodingContext(new byte [] { 0x00 }), decodingHypothesis);
			} else {
				value = super.decodeBooleanValue(new DecodingContext(new byte [] { buffer[3] }), decodingHypothesis);
			}
			_logger.exiting("LibItsCommon_TypesAndValuesCodec", "decode", value.toString());
			return value;
		} catch (Exception e) {
			e.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("LibItsCommon_TypesAndValuesCodec", "encodeRecordValue", String.format("%s - %s", rv.getType().getName(), rv.getValueEncoding()));

		if ((rv.getValueEncoding() != null) && (rv.getValueEncoding() != encodingContext.getValueEncoding())) {
			// Retrieve the correct codec
			TciCDProvided codec = getCodec(rv.getValueEncoding());
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for encoding name %s", rv.getValueEncoding()));
			}
			// And continue encoding operations
			TriMessage value = codec.encode(rv);
			try {
				encodingContext.setBytes(value.getEncodedMessage());
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			try { // Type
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(rv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			}
			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();
				super.encodeRecordValue(out_rv, rv);
				try { // Length
					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("LibItsCommon_TypesAndValuesCodec.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("LibItsCommon_TypesAndValuesCodec", "encodeUnionValue", String.format("%s - %s", uv.getType().getName(), uv.getValueEncoding()));

		if ((uv.getValueEncoding() != null) && (uv.getValueEncoding() != encodingContext.getValueEncoding())) {
			// Retrieve the correct codec
			TciCDProvided codec = getCodec(uv.getValueEncoding());
			if (codec == null) {
				throw new RuntimeException(String.format("Could not retrieve codec for encoding name %s", uv.getValueEncoding()));
			}
			// And continue encoding operations
			TriMessage value = codec.encode(uv);
			try {
				encodingContext.setBytes(value.getEncodedMessage());
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			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();
					}
				}
			}
		}

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

} // End of class LibItsCommon_TypesAndValuesCodec
