/**
 * @author	STF 424_ITS_Test_Platform
 * @version    $URL:$
 *             $Id:$
 */
package org.etsi.its.codec.ttcn.LibItsDenm_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.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 LibItsDenm_TypesAndValues TTCN-3 module (DENM messages)
 * 
 * Note that it requires closely linked to DENM ASN.1 codec
 * 
 * See 'ETSI TS 102 868-3 Clause 4.4.2 Primitives of the utPort' for UpperTester primitives for DENM
 * 
 * Refer to ETSI ES 201 873-6 for TCI implementations
 * @category Codec
 */
public class LibItsDenm_TypesAndValuesCodec extends ItsCodecImpl {

	/**
	 * Constructor
	 * @param typeServer Reference to the tool specific codec implementation
	 */
	public LibItsDenm_TypesAndValuesCodec(TciCDRequired typeServer) {
		super(typeServer);
		
		_logger.entering("LibItsDenm_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("LibItsDenm_TypesAndValuesCodec", "encode", template.toString());

		try {
//			if (!initEncoding()) {
//				_logger.severe("Could not be initialised correctly");
//				return null;
//			}
			TriMessage encoded = super.encode(template);
			if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsDenm_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("LibItsDenm_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("LibItsDenm_TypesAndValuesCodec.decode: ", encodedMsg);
		throw new RuntimeException("LibItsDenm_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("LibItsDenm_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 if (rv.getType().getName() == "DenmPdu") {
			super.encodeRecordValue(encodingContext, rv);
		} else {
			try {
				if (rv.getType().getName() == "_0DecentralizedSituation") { // FIXME Optimise this!!!!
					encodingContext.setByte(TestAdapterMessageTypeEnum.eventCharact.convert());
				} else {
					encodingContext.setByte(TestAdapterMessageTypeEnum.convert(rv.getType().getName())); // Type
				}
			} 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();
				if (
					(rv.getType().getName() == TestAdapterMessageTypeEnum.Situation.name()) || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.EventTimeFrequency.name()) || 
					(rv.getType().getName() == "_0DecentralizedSituation") || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.ActionID.name()) || 
					(rv.getType().getName() == TestAdapterMessageTypeEnum.UpdateEventExpiry.name())) {
					for (String fieldName : rv.getFieldNames()) {
						Value fieldValue = rv.getField(fieldName);
						switch(fieldValue.getType().getTypeClass()) {
							case TciTypeClass.ENUMERATED:
							case TciTypeClass.INTEGER:
								encodeIntegerValue(out_rv, fieldName, (IntegerValue)fieldValue);
								break;
							case TciTypeClass.BOOLEAN:
								encodeBooleanValue(out_rv, fieldName, (BooleanValue)fieldValue);
								break;
							case TciTypeClass.RECORD:
								encodeRecordValue(out_rv, (RecordValue) fieldValue);
								break;
							default:
								throw new RuntimeException(String.format("Not implemented type %s - %d", fieldValue.getType().getName(), fieldValue.getType().getTypeClass()));
						}
					} // End of 'for' statement
				} else {
					super.encodeRecordValue(out_rv, rv);
				}
				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();
					}
				}
			}
		}

		if (_logger.isLoggable(Level.ALL)) ByteHelper.dump("LibItsDenm_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("LibItsDenm_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 processing
			TriMessage value = codec.encode(uv);
			try {
				encodingContext.setBytes(value.getEncodedMessage());
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else if (
				(uv.getType().getName() == TestAdapterMessageTypeEnum.UtDenmEvent.name()) || 
				(uv.getType().getName() == TestAdapterMessageTypeEnum.EventOption.name())) {
			_logger.info(String.format("Process union %s - %s", uv.getType().getName(), uv.getPresentVariantName()));
			try {
				encodingContext.setByte(TestAdapterMessageTypeEnum.convert(uv.getType().getName()));
			} catch (IOException e) {
				e.printStackTrace();
			} // Type
			EncodingContext out_uv = new EncodingContext();
			try {
				if (uv.getVariant(uv.getPresentVariantName()).getType().getName() == TestAdapterMessageTypeEnum.EventTimeFrequency.name()) {
					out_uv.setByte(TestAdapterMessageTypeEnum.EventTimeFrequency.convert()); // Type
				} else {
					out_uv.setByte(TestAdapterMessageTypeEnum.valueOf(uv.getPresentVariantName()).convert()); // Type
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			EncodingContext out_uv1 = new EncodingContext();
			if (
					(uv.getPresentVariantName() == TestAdapterMessageTypeEnum.setTurnAdvice.name()) || 
					(uv.getPresentVariantName() == TestAdapterMessageTypeEnum.setPtLineDescription.name())) {
				RecordValue rv = (RecordValue) uv.getVariant(uv.getPresentVariantName());
				for (String item : rv.getFieldNames()) {
					try {
						out_uv1.setByte(TestAdapterMessageTypeEnum.valueOf(item).convert());
					} catch (IOException e) {
						e.printStackTrace();
					} // Type
					EncodingContext out_uv2 = new EncodingContext();
					Value value = rv.getField(item);
					switch (value.getType().getTypeClass()) {
						case TciTypeClass.BITSTRING:
							super.encodeBitstringValue(out_uv2, (BitstringValue) value); // Value
							out_uv2.finalizeBitstringValueEncoding();
							break;
						case TciTypeClass.INTEGER:
							super.encodeIntegerValue(out_uv2, (IntegerValue) rv.getField(item)); // Value
							break;
						case TciTypeClass.CHARSTRING:
							super.encodeCharstringValue(out_uv2, (CharstringValue) rv.getField(item)); // 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 e1) {
						e1.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() == TestAdapterMessageTypeEnum.setDoorStatus.name()) || 
					(uv.getPresentVariantName() == TestAdapterMessageTypeEnum.setExteriorLightsStatus.name())) {
				super.encodeBitstringValue(out_uv1, (BitstringValue) uv.getVariant(uv.getPresentVariantName())); // Value
				out_uv1.finalizeBitstringValueEncoding();
//			} else if (uv.getPresentVariantName() == "eventCancellation") {
//				super.encodeIntegerValue(out_uv1, (IntegerValue) uv.getVariant(uv.getPresentVariantName()), _bitpos); // Value
			} 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 { // Length
				encodingContext.setBytes((byte[])ByteHelper.intToByteArray(out_uv.size(), LengthFieldSize));
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			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("LibItsDenm_TypesAndValuesCodec.encodeUnionValue: ", encodingContext.toByteArray());
	} // End of method encodeUnionValue

} // End of class LibItsDenm_TypesAndValuesCodec
