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

import java.io.IOException;
import java.math.BigInteger;

import org.etsi.ttcn.tci.BitstringValue;
import org.etsi.ttcn.tci.BooleanValue;
import org.etsi.ttcn.tci.CharValue;
import org.etsi.ttcn.tci.CharstringValue;
import org.etsi.ttcn.tci.EnumeratedValue;
import org.etsi.ttcn.tci.FloatValue;
import org.etsi.ttcn.tci.HexstringValue;
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.TciCDProvided;
import org.etsi.ttcn.tci.TciCDRequired;
import org.etsi.ttcn.tci.TciTypeClass;
import org.etsi.ttcn.tci.TciValueTemplate;
import org.etsi.ttcn.tci.Type;
import org.etsi.ttcn.tci.UnionValue;
import org.etsi.ttcn.tci.UniversalCharstringValue;
import org.etsi.ttcn.tci.Value;
import org.etsi.ttcn.tri.TriMessage;

/**
 * This class provides a basic codec implementation
 * Specialised codec shall extends this class
 *
 * @see "<code>ICodec</code> interface"
 * 
 * @category Codec
 */
public class BasicCodecImpl implements ICodec, TciCDProvided {
	
	private TciCDRequired _typeServer;
	
	/**
	 * Constructor
	 * @param typeServer
	 */
	public BasicCodecImpl(TciCDRequired typeServer) {
		_typeServer = typeServer;
	}
	
	@Override
	public BitstringValue decodeBitstringValue(DecodingContext decodingContext, int decodingLength) {
		BitstringValue bitstring = (BitstringValue)_typeServer.getBitstring().newInstance();

		bitstring.setLength(decodingLength);
		byte[] bitArray = decodingContext.getBits(decodingLength);
		for (int i = 0; i < bitArray.length; i++) {
			bitstring.setBit(i, (int)bitArray[i]);
		}

		return bitstring;
	}

	@Override
	public BitstringValue decodeBitstringValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		BitstringValue bitstring = (BitstringValue)decodingHypothesis.newInstance();

		bitstring.setLength(decodingLength);
		byte[] bitArray = decodingContext.getBits(decodingLength);
		for (int i = 0; i < bitArray.length; i++) {
			bitstring.setBit(i, (int)bitArray[i]);
		}
		
		return bitstring;
	}

	@Override
	public BooleanValue decodeBooleanValue(DecodingContext decodingContext, int decodingLength) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeBooleanValue: Trying to decoded a boolean value not byte aligned or with a non integral number of bytes");
		}

		byte[] theBooleanArray = decodingContext.getNextBytes(decodingLength >> 3);
		BooleanValue booleanValue = (BooleanValue)_typeServer.getBoolean().newInstance();
		// Get the byte from the byte array defined by the _bitpos 
		int i = new BigInteger(theBooleanArray).intValue();;
		boolean value = false;
		if (i == 0) {
			value = false;
		} else if (i == 1) {
			value = true;
		} else {
			throw new RuntimeException("BasicCodecImpl.decodeBooleanValue: Cannot decode the boolean");
		}
		booleanValue.setBoolean(value);
		
		return booleanValue;
	}

	@Override
	public BooleanValue decodeBooleanValue(DecodingContext decodingContext) {
		BooleanValue booleanValue = (BooleanValue)_typeServer.getBoolean().newInstance();

		// Get the byte from the byte array defined by the bitpos 
		int i = decodingContext.getNextByte();
		boolean value = false;
		if (i == 0) {
			value = false;
		} else if (i == 1) {
			value = true;
		} else {
			throw new RuntimeException("BasicCodecImpl.decodeBooleanValue (2): Cannot decode the boolean");
		}
		booleanValue.setBoolean(value);
		
		return booleanValue;
	}

	@Override
	public BooleanValue decodeBooleanValue(DecodingContext decodingContext, Type decodingHypothesis) {
		BooleanValue booleanValue = (BooleanValue)decodingHypothesis.newInstance();
		
		boolean value = false;
		// Get the byte from the byte array defined by the bitpos 
		int i = decodingContext.getNextByte();
		if (i == 0) {
			value = false;
		} else if (i == 1) {
			value = true;
		} else {
			throw new RuntimeException("BasicCodecImpl.decodeBooleanValue (3): Cannot decode the boolean");
		}
		booleanValue.setBoolean(value);
		
		return booleanValue;
	}

	@Override
	public CharValue decodeCharValue(DecodingContext decodingContext, int decodingLength) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeCharValue: Trying to decoded a CharValue value not byte aligned or with a non integral number of bytes");
		}

		CharValue charstring = (CharValue)_typeServer.getChar().newInstance();
		String str = new String(decodingContext.getNextBytes(decodingLength / Byte.SIZE));
		charstring.setChar(str.charAt(0)); // FIXME To be tested !!!!

		return charstring;
	}

	@Override
	public CharValue decodeCharValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeCharValue (1): Trying to decoded a CharValue value not byte aligned or with a non integral number of bytes");
		}

		CharValue charstring = (CharValue)decodingHypothesis.newInstance();
		String str = new String(decodingContext.getNextBytes(decodingLength / Byte.SIZE));
		charstring.setChar(str.charAt(0)); // FIXME To be tested !!!!

		return charstring;
	}

	@Override
	public CharstringValue decodeCharstringValue(DecodingContext decodingContext, int decodingLength) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeCharstringValue: Trying to decoded a CharstringValue value not byte aligned or with a non integral number of bytes");
		}

		// Copy the bytes of the charstring into a new byte array
		byte[] theCharstringArray = decodingContext.getNextBytes(decodingLength >> 3);
		CharstringValue charstring = (CharstringValue)_typeServer.getCharstring().newInstance();
		charstring.setString(new String(theCharstringArray));
		
		return charstring;
	}

	@Override
	public CharstringValue decodeCharstringValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeCharstringValue (1): Trying to decoded a CharstringValue value not byte aligned or with a non integral number of bytes");
		}

		// Copy the bytes of the charstring into a new byte array
		byte[] theCharstringArray = decodingContext.getNextBytes(decodingLength >> 3);
		CharstringValue charstring = (CharstringValue)decodingHypothesis.newInstance();
		charstring.setString(new String(theCharstringArray));
		
		return charstring;
	}

	@Override
	public FloatValue decodeFloatValue(DecodingContext decodingContext, int decodingLength) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeFloatValue: Trying to decoded a FloatValue value not byte aligned or with a non integral number of bytes");
		}

		byte[] theFloatArray = decodingContext.getNextBytes(decodingLength >> 3);
		FloatValue floatValue = (FloatValue)_typeServer.getFloat().newInstance();
		float theFloat = Float.intBitsToFloat(new BigInteger(theFloatArray).intValue());
		floatValue.setFloat(theFloat);
		
		return floatValue;
	}

	@Override
	public FloatValue decodeFloatValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeFloatValue (1): Trying to decoded a FloatValue value not byte aligned or with a non integral number of bytes");
		}

		byte[] theFloatArray = decodingContext.getNextBytes(decodingLength >> 3);
		FloatValue floatValue = (FloatValue)decodingHypothesis.newInstance();
		float theFloat = Float.intBitsToFloat(new BigInteger(theFloatArray).intValue());
		floatValue.setFloat(theFloat);
		
		return floatValue;
	}

	@Override
	public HexstringValue decodeHexstringValue(DecodingContext decodingContext, int decodingLength) {
		HexstringValue hexstring = (HexstringValue)_typeServer.getHexstring().newInstance();
		
		if (decodingLength == 0) {
			hexstring.setLength(0);
			return hexstring;
		}
		
		byte[] hexaString = decodingContext.getHexaString(decodingLength);
		if (hexaString != null) {
			hexstring.setLength(hexaString.length);
			for (int i = 0; i < hexaString.length; i++)
				hexstring.setHex(i, hexaString[i]);
		}
		
		return hexstring;
	}

	@Override
	public HexstringValue decodeHexstringValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		HexstringValue hexstring = (HexstringValue)decodingHypothesis.newInstance();
		
		if (decodingLength == 0) {
			hexstring.setLength(0);
			return hexstring;
		}
		
		byte[] hexaString = decodingContext.getHexaString(decodingLength);
		if (hexaString != null) {
			hexstring.setLength(hexaString.length);
			for (int i = 0; i < hexaString.length; i++)
				hexstring.setHex(i, hexaString[i]);
		}
		
		return hexstring;
	}

	@Override
	public IntegerValue decodeIntegerValue(DecodingContext decodingContext, int decodingLength) {
		if (!(decodingContext.isPositionAligned() && ((decodingLength % 8) == 0))) {
			throw new RuntimeException("BasicCodecImpl.decodeIntegerValue (1): Trying to decoded a IntegerValue value not byte aligned or with a non integral number of bytes");
		}

		IntegerValue integer = (IntegerValue)_typeServer.getInteger().newInstance();
		// Copy the 'decodingLength/8' bytes of the integer in the encoded message into a new byte array
		byte[] theIntArray = decodingContext.getNextBytes(decodingLength >> 3);
		Integer theInt = (new BigInteger(theIntArray)).intValue();
		integer.setInt(theInt);
		
		return integer;
	}

	@Override
	public IntegerValue decodeIntegerValue(DecodingContext decodingContext) {
		return decodeIntegerValue(decodingContext, 4 << 3);
	}

	@Override
	public IntegerValue decodeIntegerValue(DecodingContext decodingContext, Type decodingHypothesis) {
		IntegerValue integer = (IntegerValue)decodingHypothesis.newInstance();
		// Copy the 4 bytes of the integer in the encoded message into a new byte array
		byte[] theIntArray = decodingContext.getNextBytes(4);
		Integer theInt = (new BigInteger(theIntArray)).intValue();
		integer.setInt(theInt);
		
		return integer;
	}

	@Override
	public OctetstringValue decodeOctetstringValue(DecodingContext decodingContext, int decodingLength) {
		OctetstringValue octetstring = (OctetstringValue)_typeServer.getOctetstring().newInstance();
		if (decodingLength == 0) {
			octetstring.setLength(0);
			return octetstring;
		}
		
		byte[] ostr = decodingContext.getOctetString(decodingLength);
		if (ostr != null) {
			octetstring.setLength(ostr.length);
			for (int i = 0; i < ostr.length; i++)
				octetstring.setOctet(i, ostr[i]);
		}
		
		return octetstring;
	}

	@Override
	public OctetstringValue decodeOctetstringValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		OctetstringValue octetstring = (OctetstringValue)decodingHypothesis.newInstance();
		if (decodingLength == 0) {
			octetstring.setLength(0);
			return octetstring;
		}
		
		byte[] ostr = decodingContext.getOctetString(decodingLength);
		if (ostr != null) {
			octetstring.setLength(ostr.length);
			for (int i = 0; i < ostr.length; i++)
				octetstring.setOctet(i, ostr[i]);
		}
		
		return octetstring;
	}

	@Override
	public UniversalCharstringValue decodeUniversalCharstringValue(DecodingContext decodingContext, int decodingLength) {
		throw new RuntimeException("BasicCodecImpl.decodeUniversalCharstringValue: Not implemented");
	}

	@Override
	public UniversalCharstringValue decodeUniversalCharstringValue(DecodingContext decodingContext, int decodingLength, Type decodingHypothesis) {
		throw new RuntimeException("BasicCodecImpl.decodeUniversalCharstringValue: Not implemented");
	}

	@Override
	public void encodeBitstringValue(EncodingContext encodingContext, final BitstringValue bv) {
		if ((bv == null) || bv.notPresent()) {
			return;
		}
		if (((TciValueTemplate) bv).isAnyOrOmit() || ((TciValueTemplate) bv).isAny()) {
			throw new RuntimeException("BasicCodecImpl.encodeBitstringValue: Can't send wildcard " + bv.getType().getName());
		}

		encodingContext.setBitString(bv.getString());
	}

	@Override
	public void encodeBooleanValue(EncodingContext encodingContext, final BooleanValue boolValue) {
		if ((boolValue == null) || boolValue.notPresent()) {
			return;
		}
		if (((TciValueTemplate) boolValue).isAnyOrOmit() || ((TciValueTemplate) boolValue).isAny()) {
			throw new RuntimeException("BasicCodecImpl.encodeBooleanValue: Can't send wildcard " + boolValue.getType().getName());
		}

		try {
			encodingContext.setByte((byte) ((boolValue.getBoolean() == true) ? 0x01 : 0x00));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void encodeCharstringValue(EncodingContext encodingContext, final CharstringValue charstringValue) {
		if ((charstringValue == null) || charstringValue.notPresent()) {
			return;
		}
		if (((TciValueTemplate) charstringValue).isAnyOrOmit() || ((TciValueTemplate) charstringValue).isAny()) {
			throw new RuntimeException("BasicCodecImpl.encodeCharstringValue: Can't send wildcard " + charstringValue.getType().getName());
		}

		final String charstring = charstringValue.getString();
		try {
			encodingContext.setBytes(charstring.getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void encodeEnumeratedValue(EncodingContext encodingContext, final EnumeratedValue value) {
		encodeIntegerValue(encodingContext, (IntegerValue)value);
	}

	@Override
	public void encodeFloatValue(EncodingContext encodingContext, final FloatValue value) {
		throw new RuntimeException("BasicCodecImpl.encodeFloatValue: Not implemented");
	}

	@Override
	public void encodeHexstringValue(EncodingContext encodingContext, final HexstringValue hv) {
		if ((hv == null) || hv.notPresent()) {
			return;
		}
		if (((TciValueTemplate) hv).isAnyOrOmit() || ((TciValueTemplate) hv).isAny()) {
			throw new RuntimeException("BasicCodecImpl.encodeCharstringValue: Can't send wildcard " + hv.getType().getName());
		}

		byte[] hvBuffer = new byte[hv.getLength()];
		for (int i = 0; i < hv.getLength(); i++) {
			hvBuffer[i] = (byte) hv.getHex(i);
		}
		encodingContext.setHexaString(hvBuffer);
	}

	@Override
	public void encodeIntegerValue(EncodingContext encodingContext, final IntegerValue intValue) {
		int i = intValue.getInt();
		byte[] intBuffer = new byte[4];
		// To store the needed bytes of the bytepattern (of Integer) in the appropiate Position in the bytearray
		intBuffer[0] = (byte) (i >> 24);
		intBuffer[1] = (byte) (i >> 16);
		intBuffer[2] = (byte) (i >> 8);
		intBuffer[3] = (byte) i;
		try {
			encodingContext.setBytes(intBuffer);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void encodeOctetstringValue(EncodingContext encodingContext, final OctetstringValue ov) {
		if ((ov == null) || ov.notPresent()) {
			return;
		}
		
		if (((TciValueTemplate)ov).isAnyOrOmit() || ((TciValueTemplate) ov).isAny()) {
			throw new RuntimeException("BasicCodecImpl.encodeOctetstringValue: Can't send wildcard " + ov.getType().getName());
		}
		
		encodingContext.setOctetString(ov.getString());
	}

	@Override
	public void encodeUniversalCharstringValue(EncodingContext encodingContext, final UniversalCharstringValue uniCharstringValue) {
		throw new RuntimeException("BasicCodecImpl.encodeUniversalCharstringValue: Not implemented");
	}

	/** 
	 * This method encodes a RecordValue in a ByteArray representation at a global bit position
	 *
	 * @param encodingContext The EncodingContext collect the ByteArray
	 * @param rv The <code>RecordValue</code> to encode
	 */
	@Override
	public void encodeRecordValue(EncodingContext encodingContext, final RecordValue rv) {
		if ((rv == null) || rv.notPresent()) {
			return;
		}

		if (((TciValueTemplate) rv).isAnyOrOmit() || ((TciValueTemplate) rv).isAny()) {
			throw new RuntimeException(String.format("BasicCodecImpl.encodeRecordValue: Can't send wildcard: '%s'", rv.getType().getName()));
		}

		String[] allFields = rv.getFieldNames();

		for (int i = 0, n = allFields.length; i < n; i++) {
			Value fieldValue = rv.getField(allFields[i]);

			encodeValue(encodingContext, fieldValue);
		}
	} // End of method encodeRecordValue

	/** 
	 * This method encodes a RecordOfValue in a ByteArray representation at a global bit position
	 *
	 * @param encodingContext The EncodingContext collect the ByteArray
	 * @param rov The <code>RecordOfValue</code> to encode
	 */
	@Override
	public void encodeRecordofValue(EncodingContext encodingContext, final RecordOfValue rov) {
		if ((rov == null) || rov.notPresent()) {
			return;
		}

		if (((TciValueTemplate) rov).isAnyOrOmit() || ((TciValueTemplate) rov).isAny()) {
			throw new RuntimeException(String.format("BasicCodecImpl.encodeRecordofValue: Can't send wildcard: '%s'", rov.getType().getName()));
		}

		for (int i = 0, n = rov.getLength(); i < n; i++) {
			Value fieldValue = rov.getField(i);

			encodeValue(encodingContext, fieldValue);
		}
	} // End of method encodeRecordofValue

	/** 
	 * This method encodes an UnionValue in a ByteArray representation at a global bit position
	 *
	 * @param encodingContext The EncodingContext collect the ByteArray
	 * @param uv The <code>UnionValue</code> to encode
	 */
	@Override
	public void encodeUnionValue(EncodingContext encodingContext, final UnionValue uv) {
		if ((uv == null) || uv.notPresent()) {
			return;
		}

		if (((TciValueTemplate) uv).isAnyOrOmit() || ((TciValueTemplate) uv).isAny()) {
			throw new RuntimeException(String.format("BasicCodecImpl.encodeUnionValue: Can't send wildcard: '%s'", uv.getType().getName()));
		}

		String fieldName = uv.getPresentVariantName();
		Value fieldValue = uv.getVariant(fieldName);

		encodeValue(encodingContext, fieldValue);
	} // End of method encodeUnionValue

	@Override
	public TciCDProvided getCodec(String encodingName) {
		return null;
	}

	@Override
	public Value decode(TriMessage message, Type decodingHypothesis) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public TriMessage encode(final Value template) {
		final EncodingContext encodingContext = new EncodingContext(template.getValueEncoding());
		encodeValue(encodingContext, template);
		encodingContext.finalyze();
		TriMessage encodedMessage = new TriMessageImpl(encodingContext.toByteArray());
		return encodedMessage;
	}

	/**
	 * This method calls the appropriate encoding method according the type value
	 * @param encodingContext The EncodingContext collect the ByteArray
	 * @param value The <code>Value</code> to encode
	 */
	private void encodeValue(EncodingContext encodingContext, final Value value) {
		if (((TciValueTemplate) value).isAnyOrOmit() || ((TciValueTemplate) value).isAny()) {
			throw new RuntimeException(String.format("BasicCodecImpl.encodeValue: Can't send wildcard: '%s'", value.getType().getName()));
		}

		if (!value.notPresent()) {
			switch (value.getType().getTypeClass()) {
				case TciTypeClass.BOOLEAN:
					encodeBooleanValue(encodingContext, (BooleanValue) value);
					break;
				case TciTypeClass.INTEGER:
					encodeIntegerValue(encodingContext, (IntegerValue) value);
					break;
				case TciTypeClass.FLOAT:
					encodeFloatValue(encodingContext, (FloatValue) value);
					break;
				case TciTypeClass.CHARSTRING:
					encodeCharstringValue(encodingContext, (CharstringValue) value);
					break;
				case TciTypeClass.UNIVERSAL_CHARSTRING:
					encodeUniversalCharstringValue(encodingContext,
							(UniversalCharstringValue) value);
					break;
				case TciTypeClass.ENUMERATED:
					encodeIntegerValue(encodingContext, (IntegerValue) value);
					break;
				case TciTypeClass.UNION:
					encodeUnionValue(encodingContext, (UnionValue) value);
					break;
				case TciTypeClass.HEXSTRING:
					encodeHexstringValue(encodingContext, (HexstringValue) value);
					break;
				case TciTypeClass.BITSTRING:
					encodeBitstringValue(encodingContext, (BitstringValue) value);
					break;
				case TciTypeClass.OCTETSTRING:
					encodeOctetstringValue(encodingContext, (OctetstringValue) value);
					break;
				case TciTypeClass.SET:
				case TciTypeClass.RECORD:
					encodeRecordValue(encodingContext, (RecordValue) value);
					break;
				case TciTypeClass.SET_OF:
				case TciTypeClass.RECORD_OF:
					encodeRecordofValue(encodingContext, (RecordOfValue) value);
					break;
				default:
					throw new RuntimeException(String.format("BasicCodecImpl.encodeValue: Not implemented type class %s - %s", value.getType().getName(), value.getType().getTypeClass()));
			} // End of 'switch' statement
		}
	} // End of method encodeValue
}
