package generated.javacard.mondex.purse;

import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

/**
 * Class for T(L)V encoding of messages and other classes
 */
public class Coding {

	/**
	 * offset into encodeDestination for writing/reading
	 */
	private short encoding_length;

	/**
	 * array used for encoding/decoding
	 */
	private byte[] encodeDestination;

	/**
	 * Singleton instance
	 */
	private static Coding the_instance;

	/**
	 * Singleton accessor
	 * 
	 * @return Singleton instance
	 */
	public static Coding getInstance() {
		if (the_instance == null)
			the_instance = new Coding();
		return the_instance;
	}

	/**
	 * return offset into encodeDestination
	 * 
	 * @return value of encoding_length
	 */
	public short getEncodingLength() {
		return encoding_length;
	}

	/**
	 * initializes encoding. This method must be called before encoding an object.
	 * The offset (encoding_length) is reset to 0 and encodeDestination is set to dest.
	 * 
	 * @param dest The (empty) destination array for encoding
	 */
	private void encodeInit(byte[] dest) {
		encodeDestination = dest;
		encoding_length = 0;
	}

	/**
	 * initializes decoding. The offset (encoding_length) is reset to 0.
	 */
	private void decodeInit() {
		encoding_length = 0;
	}

	/**
	 * initializes encoding and encodes an object.
	 * 
	 * @param o The object to encode
	 * @param dest destination array for the encoding
	 * @return length of the encoded object
	 */
	public short encode(Codeable o, byte[] dest) {
		encodeInit(dest);
		try {
			encode(o);
		} catch (ArrayIndexOutOfBoundsException e) {
			stop();
		}
		return encoding_length;
	}

	/**
	 * computes the encoding length of an object without actually encoding it.
	 * 
	 * @param o The object of which the encoding length should be computed
	 * @return encoding length
	 */
	private short computeSize(Codeable o) {
		encoding_length = 0;
		try {
			compSize(o);
			return encoding_length;
		} catch (Exception e) {
			return -1;
		}
	}

	private void encodeShort(short o) {
		encodeDestination[encoding_length++] = Code.SHORT;
		Util.setShort(encodeDestination, encoding_length, o);
		encoding_length += 2;
	}

	private void encodeByte(byte o) {
		encodeDestination[encoding_length++] = Code.BYTE;
		encodeDestination[encoding_length++] = o;
	}

	private void encodeBoolean(boolean o) {
		encodeDestination[encoding_length++] = Code.BOOLEAN;
		encodeDestination[encoding_length++] = (byte) (o ? 1 : 0);
	}

	private void compSizeShort(short o) {
		encoding_length += 3;
	}

	private void compSizeByte(byte o) {
		encoding_length += 2;
	}

	private void compSizeBoolean(boolean o) {
		encoding_length += 2;
	}

	private short decodeShort(byte[] in) {
		if (in[encoding_length++] != Code.SHORT)
			stop();
		short res = Util.getShort(in, encoding_length);
		encoding_length += 2;
		return res;
	}

	private byte decodeByte(byte[] in) {
		if (in[encoding_length++] != Code.BYTE)
			stop();
		byte res = in[encoding_length++];
		return res;
	}

	private boolean decodeBoolean(byte[] in) {
		if (in[encoding_length++] != Code.BOOLEAN)
			stop();
		boolean res = (in[encoding_length++] == 1 ? true : false);
		return res;
	}

	private void encodeByteArray(byte[] o) {
		encodeDestination[encoding_length++] = Code.BYTEARRAY;
		Util.setShort(encodeDestination, encoding_length, (short) (o.length));
		encoding_length += 2;
		Util.arrayCopy(o, (short) 0, encodeDestination, encoding_length,
				(short) o.length);
		encoding_length += o.length;
	}

	private void compSizeByteArray(byte[] o) {
		encoding_length += 3;
		encoding_length += o.length;
	}

	private void decodeByteArrayInto(byte[] in, byte[] into) {
		if (in[encoding_length++] != Code.BYTEARRAY)
			stop();
		short len = Util.getShort(in, encoding_length);
		encoding_length += 2;
		Util.arrayCopy(in, encoding_length, into, (short) 0, len);
		encoding_length += len;
	}

	private void encode(Codeable c) {
		switch (c.getCode()) {
			case Code.GETBALANCE :
				encodeGetBalance((GetBalance) c);
				break;
			case Code.RESGETBALANCE :
				encodeResGetBalance((ResGetBalance) c);
				break;
			case Code.GETDATA :
				encodeGetData((GetData) c);
				break;
			case Code.STARTFROM :
				encodeStartFrom((StartFrom) c);
				break;
			case Code.STARTTO :
				encodeStartTo((StartTo) c);
				break;
			case Code.REQ :
				encodeReq((Req) c);
				break;
			case Code.VAL :
				encodeVal((Val) c);
				break;
			case Code.ACK :
				encodeAck((Ack) c);
				break;
			case Code.RESGETDATA :
				encodeResGetData((ResGetData) c);
				break;
			case Code.PURSEDATA :
				encodePurseData((PurseData) c);
				break;
			case Code.ENCDATASYMM :
				encodeEncDataSymm((EncDataSymm) c);
				break;
			case Code.MSGCONTENT :
				encodeMsgcontent((Msgcontent) c);
				break;
			case Code.PAYDETAILS :
				encodePayDetails((PayDetails) c);
				break;
			case Code.SYMMKEY :
				encodeSymmkey((Symmkey) c);
				break;
			// never reached
			default :
				stop();
				break;
		}
	}

	public Message decodeMessage(byte[] in, short offset, short expectedLength) {
		encoding_length = offset;
		Message m = null;
		try {
			switch (in[encoding_length]) {
				case Code.GETBALANCE :
					m = Store.newGetBalance();
					decodeGetBalanceInto(in, (GetBalance) m);
					break;
				case Code.RESGETBALANCE :
					m = Store.newResGetBalance();
					decodeResGetBalanceInto(in, (ResGetBalance) m);
					break;
				case Code.GETDATA :
					m = Store.newGetData();
					decodeGetDataInto(in, (GetData) m);
					break;
				case Code.STARTFROM :
					m = Store.newStartFrom();
					decodeStartFromInto(in, (StartFrom) m);
					break;
				case Code.STARTTO :
					m = Store.newStartTo();
					decodeStartToInto(in, (StartTo) m);
					break;
				case Code.REQ :
					m = Store.newReq();
					decodeReqInto(in, (Req) m);
					break;
				case Code.VAL :
					m = Store.newVal();
					decodeValInto(in, (Val) m);
					break;
				case Code.ACK :
					m = Store.newAck();
					decodeAckInto(in, (Ack) m);
					break;
				case Code.RESGETDATA :
					m = Store.newResGetData();
					decodeResGetDataInto(in, (ResGetData) m);
					break;
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			stop();
		}
		if (m == null || encoding_length != (short) (expectedLength + offset))
			stop();
		return m;
	}

	public PlainData decodePlainData(byte[] in, byte expectedType) {
		encoding_length = 0;
		PlainData p = null;
		try {
			if (in[encoding_length] != expectedType)
				stop();
			switch (in[encoding_length]) {
				case Code.MSGCONTENT :
					p = Store.newMsgcontent();
					decodeMsgcontentInto(in, (Msgcontent) p);
					break;
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			stop();
		}
		if (p == null)
			stop();
		return p;
	}

	private void compSize(Codeable c) {
		switch (c.getCode()) {
			case Code.GETBALANCE :
				compSizeGetBalance((GetBalance) c);
				break;
			case Code.RESGETBALANCE :
				compSizeResGetBalance((ResGetBalance) c);
				break;
			case Code.GETDATA :
				compSizeGetData((GetData) c);
				break;
			case Code.STARTFROM :
				compSizeStartFrom((StartFrom) c);
				break;
			case Code.STARTTO :
				compSizeStartTo((StartTo) c);
				break;
			case Code.REQ :
				compSizeReq((Req) c);
				break;
			case Code.VAL :
				compSizeVal((Val) c);
				break;
			case Code.ACK :
				compSizeAck((Ack) c);
				break;
			case Code.RESGETDATA :
				compSizeResGetData((ResGetData) c);
				break;
			case Code.PURSEDATA :
				compSizePurseData((PurseData) c);
				break;
			case Code.ENCDATASYMM :
				compSizeEncDataSymm((EncDataSymm) c);
				break;
			case Code.MSGCONTENT :
				compSizeMsgcontent((Msgcontent) c);
				break;
			case Code.PAYDETAILS :
				compSizePayDetails((PayDetails) c);
				break;
			case Code.SYMMKEY :
				compSizeSymmkey((Symmkey) c);
				break;
			// never reached
			default :
				stop();
				break;
		}
	}

	private void encodeGetBalance(GetBalance c) {
		encodeDestination[encoding_length++] = Code.GETBALANCE;
	}

	private void decodeGetBalanceInto(byte[] in, GetBalance into) {
		if (in[encoding_length++] != Code.GETBALANCE)
			stop();
	}

	private void compSizeGetBalance(GetBalance c) {
		encoding_length++;
	}

	private void encodeResGetBalance(ResGetBalance c) {
		encodeDestination[encoding_length++] = Code.RESGETBALANCE;
		encodeShort((short) c.balance);
	}

	private void decodeResGetBalanceInto(byte[] in, ResGetBalance into) {
		if (in[encoding_length++] != Code.RESGETBALANCE)
			stop();
		into.balance = decodeShort(in);
	}

	private void compSizeResGetBalance(ResGetBalance c) {
		encoding_length++;
		compSizeShort(c.balance);
	}

	private void encodeGetData(GetData c) {
		encodeDestination[encoding_length++] = Code.GETDATA;
	}

	private void decodeGetDataInto(byte[] in, GetData into) {
		if (in[encoding_length++] != Code.GETDATA)
			stop();
	}

	private void compSizeGetData(GetData c) {
		encoding_length++;
	}

	private void encodeStartFrom(StartFrom c) {
		encodeDestination[encoding_length++] = Code.STARTFROM;
		encodeShort((short) c.value);
		encode(c.dataTo);
	}

	private void decodeStartFromInto(byte[] in, StartFrom into) {
		if (in[encoding_length++] != Code.STARTFROM)
			stop();
		into.value = decodeShort(in);
		decodePurseDataInto(in, (PurseData) (into.dataTo));
	}

	private void compSizeStartFrom(StartFrom c) {
		encoding_length++;
		compSizeShort(c.value);
		compSize(c.dataTo);
	}

	private void encodeStartTo(StartTo c) {
		encodeDestination[encoding_length++] = Code.STARTTO;
		encode(c.encmess);
	}

	private void decodeStartToInto(byte[] in, StartTo into) {
		if (in[encoding_length++] != Code.STARTTO)
			stop();
		decodeEncDataSymmInto(in, (EncDataSymm) (into.encmess));
	}

	private void compSizeStartTo(StartTo c) {
		encoding_length++;
		compSize(c.encmess);
	}

	private void encodeReq(Req c) {
		encodeDestination[encoding_length++] = Code.REQ;
		encode(c.encmess);
	}

	private void decodeReqInto(byte[] in, Req into) {
		if (in[encoding_length++] != Code.REQ)
			stop();
		decodeEncDataSymmInto(in, (EncDataSymm) (into.encmess));
	}

	private void compSizeReq(Req c) {
		encoding_length++;
		compSize(c.encmess);
	}

	private void encodeVal(Val c) {
		encodeDestination[encoding_length++] = Code.VAL;
		encode(c.encmess);
	}

	private void decodeValInto(byte[] in, Val into) {
		if (in[encoding_length++] != Code.VAL)
			stop();
		decodeEncDataSymmInto(in, (EncDataSymm) (into.encmess));
	}

	private void compSizeVal(Val c) {
		encoding_length++;
		compSize(c.encmess);
	}

	private void encodeAck(Ack c) {
		encodeDestination[encoding_length++] = Code.ACK;
		encode(c.encmess);
	}

	private void decodeAckInto(byte[] in, Ack into) {
		if (in[encoding_length++] != Code.ACK)
			stop();
		decodeEncDataSymmInto(in, (EncDataSymm) (into.encmess));
	}

	private void compSizeAck(Ack c) {
		encoding_length++;
		compSize(c.encmess);
	}

	private void encodeResGetData(ResGetData c) {
		encodeDestination[encoding_length++] = Code.RESGETDATA;
		encode(c.dataTo);
	}

	private void decodeResGetDataInto(byte[] in, ResGetData into) {
		if (in[encoding_length++] != Code.RESGETDATA)
			stop();
		decodePurseDataInto(in, (PurseData) (into.dataTo));
	}

	private void compSizeResGetData(ResGetData c) {
		encoding_length++;
		compSize(c.dataTo);
	}

	private void encodePurseData(PurseData c) {
		encodeDestination[encoding_length++] = Code.PURSEDATA;
		encodeByteArray((byte[]) c.name);
		encodeShort((short) c.nextSeqNo);
	}

	private void decodePurseDataInto(byte[] in, PurseData into) {
		if (in[encoding_length++] != Code.PURSEDATA)
			stop();
		decodeByteArrayInto(in, into.name);
		into.nextSeqNo = decodeShort(in);
	}

	private void compSizePurseData(PurseData c) {
		encoding_length++;
		compSizeByteArray(c.name);
		compSizeShort(c.nextSeqNo);
	}

	private void encodeEncDataSymm(EncDataSymm c) {
		encodeDestination[encoding_length++] = Code.ENCDATASYMM;
		encodeByteArray(c.encrypted);
		encodeShort(c.plainlength);
		encodeShort(c.enclength);
		encodeByte(c.plainDataType);
	}

	private void decodeEncDataSymmInto(byte[] in, EncDataSymm into) {
		if (in[encoding_length++] != Code.ENCDATASYMM)
			stop();
		//public byte[] encrypted;
		//public short plainlength;
		//public short enclength;
		//public byte plainDataType;
		decodeByteArrayInto(in, into.encrypted);
		into.plainlength = decodeShort(in);
		into.enclength = decodeShort(in);
		byte b = decodeByte(in);
		into.plainDataType = b;
		if (b != Code.MSGCONTENT)
			stop();
	}

	private void compSizeEncDataSymm(EncDataSymm c) {
		encoding_length++;
		compSizeByteArray(c.encrypted);
		encoding_length += 8; // short + short + byte
	}

	private void encodeMsgcontent(Msgcontent c) {
		encodeDestination[encoding_length++] = Code.MSGCONTENT;
		encodeShort((short) c.msgflag);
		encode(c.pd);
	}

	private void decodeMsgcontentInto(byte[] in, Msgcontent into) {
		if (in[encoding_length++] != Code.MSGCONTENT)
			stop();
		into.msgflag = decodeShort(in);
		decodePayDetailsInto(in, (PayDetails) (into.pd));
	}

	private void compSizeMsgcontent(Msgcontent c) {
		encoding_length++;
		compSizeShort(c.msgflag);
		compSize(c.pd);
	}

	private void encodePayDetails(PayDetails c) {
		encodeDestination[encoding_length++] = Code.PAYDETAILS;
		encodeShort((short) c.value);
		encode(c.from);
		encode(c.to);
	}

	private void decodePayDetailsInto(byte[] in, PayDetails into) {
		if (in[encoding_length++] != Code.PAYDETAILS)
			stop();
		into.value = decodeShort(in);
		decodePurseDataInto(in, (PurseData) (into.from));
		decodePurseDataInto(in, (PurseData) (into.to));
	}

	private void compSizePayDetails(PayDetails c) {
		encoding_length++;
		compSizeShort(c.value);
		compSize(c.from);
		compSize(c.to);
	}

	private void encodeSymmkey(Symmkey c) {
		encodeDestination[encoding_length++] = Code.SYMMKEY;
		// public byte[] key; - ok
		// private DESKey apikey; - ignored
		encodeByteArray(c.key);
	}

	private void decodeSymmkeyInto(byte[] in, Symmkey into) {
		if (in[encoding_length++] != Code.SYMMKEY)
			stop();
		// public byte[] key; - ok
		// private DESKey apikey; - handled by updateKey() below
		decodeByteArrayInto(in, into.key);
		into.updateKey();
	}

	private void compSizeSymmkey(Symmkey c) {
		encoding_length++;
		// public byte[] key; - ok
		// private DESKey apikey; - ignored
		compSizeByteArray(c.key);
	}

	public void decodePurse(byte[] in, short offset, Purse thisPurse) {
		encoding_length = offset;
		if (in[encoding_length++] != Code.PURSE)
			stop();

		if (in[encoding_length] != Code.IGNORE) {
			decodePurseDataInto(in, thisPurse.data);
		} else
			encoding_length++;

		if (in[encoding_length] != Code.IGNORE) {
			decodeSymmkeyInto(in, thisPurse.sesskey);
		} else
			encoding_length++;

		if (in[encoding_length] != Code.IGNORE) {
			thisPurse.state = decodeByte(in);
		} else
			encoding_length++;
	}

	private void stop() {
		ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
	}
}