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

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

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.BitstringValue;
import org.etsi.ttcn.tci.BooleanValue;
import org.etsi.ttcn.tci.CharstringValue;
import org.etsi.ttcn.tci.IntegerValue;
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 LibItsCam_TypesAndValues TTCN-3 module (CAM messages)
 * 
 * Note that it requires closely linked to ASN.1 codecs (CAM and DENM)
 * 
 * See 'ETSI TS 102 868-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for CAM
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsCam_TypesAndValuesCodec extends ItsCodecImpl {

	/**
	 * Constructor
	 * @param typeServer Reference to the tool specific codec implementation
	 */
	public LibItsCam_TypesAndValuesCodec(TciCDRequired typeServer) {
		super(typeServer);
		
		_logger.entering("LibItsCam_TypesAndValuesCodec (1)", "Constructor", String.format("version: %s", Version));
	} // End of ctor

	/** 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("LibItsCam_TypesAndValuesCodec", "encode", template.toString());

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

		byte[] encodedMsg = message.getEncodedMessage();
		if (encodedMsg.length == 0) {
			_logger.severe("encodedMsg is empty");
			return null;
		}
		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsCam_TypesAndValuesCodec.decode: ", encodedMsg);
		throw new RuntimeException("LibItsCam_TypesAndValuesCodec.decode not implemented");
	} // 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("LibItsCam_TypesAndValuesCodec", "encodeRecordValue", String.format("%s - %s", rv.getType().getName(), rv.getValueEncoding()));

		if (rv.getType().getName() == "CamPdu") {
			super.encodeRecordValue(encodingContext, rv);
		} else {
			if (rv.getType().getName() == "_0CoopAwareness") {
				try {
					encodingContext.setByte(TestAdapterMessageTypeEnum.StationCharacteristics.convert());
				} catch (IOException e) {
					e.printStackTrace();
				} // Type
			} else {
				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 if (
					(rv.getType().getName() == TestAdapterMessageTypeEnum.ItsPduHeader.name()) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.CoopAwareness.name())) || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.ReferencePosition.name()) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.Longitude.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.Latitude.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.CamParameters.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.YawRate.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.TurnAdvice.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.YawRate.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.PosConfidenceEllipse.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.BasicVehicle.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.EmergencyVehicle.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.PublicTransportVehicle.name())) || 
					(rv.getType().getName().endsWith(TestAdapterMessageTypeEnum.VehicleCommonParameters.name()))) {
				_logger.info(String.format("Process record: %s", rv.getType().getName()));
				EncodingContext out_rv = new EncodingContext();
				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.UNION:
								//Do not call encodeUnionValue otherwise the element will be encoded in ASN.1
								super.encodeUnionValue(out_rv1, (UnionValue) fieldValue);
								break;
							case TciTypeClass.ENUMERATED:
								// No break;
							case TciTypeClass.INTEGER:
								encodeIntegerValue(out_rv1, fieldName, (IntegerValue) fieldValue);
								break;
							case TciTypeClass.BOOLEAN:
								encodeBooleanValue(out_rv1, fieldName, (BooleanValue) fieldValue);
								break;
							case TciTypeClass.CHARSTRING:
								encodeCharstringValue(out_rv1, fieldName, (CharstringValue) fieldValue);
								break;
							case TciTypeClass.BITSTRING:
								encodeBitstringValue(out_rv1, fieldName, (BitstringValue) 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
				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 {
				_logger.info(String.format("Process record (default): %s", rv.getType().getName()));
				EncodingContext out_rv = new EncodingContext();
				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();
					}
				}
			}
		}

		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump(String.format("LibItsCam_TypesAndValuesCodec.encodeRecordValue (%s): ", rv.getType().getName()), 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("LibItsCam_TypesAndValuesCodec", "encodeUnionValue", String.format("%s - %s", uv.getType().getName(), uv.getValueEncoding()));

		if (uv.getType().getName() == "UtCamEvent") {
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(uv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			EncodingContext out_uv = new EncodingContext();
			try {
				out_uv.setByte(TestAdapterMessageTypeEnum.valueOf(uv.getPresentVariantName()).convert());
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			EncodingContext out_uv1 = new EncodingContext();
			if (
					(uv.getPresentVariantName() == "setTurnAdvice") || 
					(uv.getPresentVariantName() == "setPtLineDescription")) {
				RecordValue rv = (RecordValue) uv.getVariant(uv.getPresentVariantName());
				for (String fieldName : rv.getFieldNames()) {
					try {
						out_uv1.setByte(TestAdapterMessageTypeEnum.valueOf(fieldName).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_uv2 = new EncodingContext();
					Value value = rv.getField(fieldName);
					switch (value.getType().getTypeClass()) {
						case TciTypeClass.BITSTRING:
							super.encodeBitstringValue(out_uv2, (BitstringValue) value); // Value
							out_uv2.finalizeBitstringValueEncoding(); // Needed to flush not aligned bits
							break;
						case TciTypeClass.INTEGER:
							super.encodeIntegerValue(out_uv2, (IntegerValue) rv.getField(fieldName)); // Value
							break;
						case TciTypeClass.CHARSTRING:
							super.encodeCharstringValue(out_uv2, (CharstringValue) rv.getField(fieldName)); // Value
							break;
						default:
							throw new RuntimeException(String.format("Not implemented type %s - %d", value.getType().getName(), value.getType().getTypeClass()));
					}
					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
			} else if (
					(uv.getPresentVariantName() == "setDoorStatus") || 
					(uv.getPresentVariantName() == "setExteriorLightsStatus")) {
				super.encodeBitstringValue(out_uv1, (BitstringValue) uv.getVariant(uv.getPresentVariantName())); // Value
				out_uv1.finalizeBitstringValueEncoding(); // Needed to flush not aligned bits
			} else {
				super.encodeUnionValue(out_uv1, uv);
			}
			try { // Length
				out_uv.setBytes((byte[])ByteHelper.intToByteArray(out_uv1.size(), LengthFieldSize));
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (out_uv1.size() != 0) { // Value
				try {
					out_uv.setBytes(out_uv1.toByteArray());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			try {
				encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_uv.size(), LengthFieldSize));
			} catch (IOException e1) {
				e1.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(String.format("LibItsCam_TypesAndValuesCodec.encodeUnionValue (%s): ", uv.getType().getName()), encodingContext.toByteArray());
	} // End of method encodeUnionValue

} // End of class LibItsCam_TypesAndValuesCodec
