package jbCAP;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import jVARIANT.*;

/** @file bCapConverter.java
 *
 *  @brief b-CAP client library
 *
 *  @version	1.3
 *	@date		2014/5/26
 *	@author		DENSO WAVE (m)
 *
 */
public final class bCapConverter {
	// header(1) + info(14) + terminator(1)
	private static final int BCAP_INFO_SIZE = 16;
	
	// {"Message Length", "Serial Number", "Reservation Area", "Function ID or Return Code", "Number of Arguments", "Arguments"}
	private static final int[] BCAP_INFO_OFFSET = new int[]{1, 5, 7, 9, 13, 15};
	
	// Byte order
	private static final ByteOrder BYTE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
	
	// Byte order for String
	private static final String	STRING_ENDIAN = "UTF-16LE";
	
	// CRC-CCITT16
	private static final int CRC_CCITT16 = 0x1021;
	
	// Size of System-reserved area
	private int m_szReserved = 0;
	
	private Deflater m_zipDeflater = new Deflater();
	private Inflater m_zipInflater = new Inflater();
	
	public bCapConverter(int szReserved){
		m_szReserved = szReserved;
	}
	
	/**
	 * Decode byte array to bCapPacket
	 * 
	 * @param bin - Byte array to be decoded
	 * @return bCapPacket decoded from the bin
	 */
	public bCapPacket Decode(byte[] bin) throws Throwable
	{
		int num, offset[];
		ArrayList<VARIANT> vntArray;
		
		// Decode information
		bCapPacket msgRet = DecodeInfo(bin);
		
		// Number of arguments
		num = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[4], 2).order(BYTE_ENDIAN).getShort();
		
		// Arguments
		offset = new int[]{BCAP_INFO_OFFSET[5]};
		
		vntArray = byte2VARIANTArray(bin, offset, num, true);
		if(vntArray != null){
			msgRet.m_vntArray = vntArray;
		}

		return msgRet;
	}
	
	/**
	 * Decode byte array to bCapPacket (only information)
	 * 
	 * @param bin - Byte array to be decoded
	 * @return bCapPacket decoded from the bin
	 */
	public bCapPacket DecodeInfo(byte[] bin) throws Throwable
	{
		int len;
		bCapPacket msgRet = new bCapPacket();
		
		// If bin is null, then throws NullPointerException
		if(bin == null){
			throw new NullPointerException();
		}
		
		len = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[0], 4).order(BYTE_ENDIAN).getInt();
		if(len > bin.length){
			len = bin.length;
		}

		// If bin is not correct format of b-CAP, then throws IllegalArgumentException
		if(bin[0] != 0x1 || bin[len - 1] != 0x4){
			throw new IllegalArgumentException();
		}
		
		// Serial number
		msgRet.m_Serial = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[1], 2).order(BYTE_ENDIAN).getShort();
		
		// Reservation area
		msgRet.m_Reserv = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[2], 2).order(BYTE_ENDIAN).getShort();
	
		// Return code
		msgRet.m_ID = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[3], 4).order(BYTE_ENDIAN).getInt();
		
		return msgRet;
	}
	
	/**
	 * Decode byte array to VARIANT array
	 * 
	 * @param bin - Byte array to be decoded
	 * @param offset - Start point of the bin
	 * @param size - Size of VARIANT array
	 * @param first - Is this function called from Decode?
	 * @return VARIANT array decoded from the bin
	 */
	private ArrayList<VARIANT> byte2VARIANTArray(byte[] bin, int[] offset, int size, boolean first) throws Throwable
	{
		ArrayList<VARIANT> vntReturn = null;
		
		if(size > 0)
		{
			int tmpVT, tmpNum;
			VARIANT vntTmp;
			
			vntReturn = new ArrayList<VARIANT>(size);
			
			for(int i = 0; i < size; i++)
			{
				vntTmp = new VARIANT();
				
				// Skip argument data length
				if(first){
					offset[0] += 4;
				}
				
				// Data type
				tmpVT = (int) ByteBuffer.wrap(bin, offset[0], 2).order(BYTE_ENDIAN).getShort();
				vntTmp.VariantPutType(tmpVT);
				offset[0] += 2;
				
				// The number of arrays
				tmpNum = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getInt();
				offset[0] += 4;
				
				// Data
				vntTmp.VariantPutObject(byte2VARIANT(bin, offset, tmpVT, tmpNum));
				
				vntReturn.add(vntTmp);
			}
		}		
		
		return vntReturn;
	}
	
	/**
	 * Decode byte array to VARIANT
	 * 
	 * @param bin - Byte array to be decoded
	 * @param offset - Start point of the bin
	 * @param vt - Data type
	 * @param size - The number of arrays
	 * @return VARIANT array decoded from the bin
	 */
	private Object byte2VARIANT(byte[] bin, int[] offset, int vt, int size) throws Throwable
	{
		Object objRet = null;
		
		if((vt & VARENUM.VT_ARRAY) != 0)
		{
			int tmpVT = vt ^ VARENUM.VT_ARRAY;
			SAFEARRAY aryTmp = null;	

			if(tmpVT == VARENUM.VT_UI1)
			{
				byte[] bytArray = new byte[size];
				
				System.arraycopy(bin, offset[0], bytArray, 0, size);
				offset[0] += size;
				
				aryTmp = new SAFEARRAY(VARENUM.VT_UI1, bytArray);
			}else{
				int i;
				Object[] objTmp = new Object[size];
				
				switch(tmpVT){
					case VARENUM.VT_I2:
					case VARENUM.VT_UI2:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 2).order(BYTE_ENDIAN).getShort();
							offset[0] += 2;
						}
						break;
					case VARENUM.VT_I4:
					case VARENUM.VT_UI4:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getInt();
							offset[0] += 4;
						}
						break;	
					case VARENUM.VT_R4:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getFloat();
							offset[0] += 4;
						}
						break;	
					case VARENUM.VT_R8:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getDouble();
							offset[0] += 8;
						}
						break;	
					case VARENUM.VT_CY:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getLong();
							offset[0] += 8;
						}
						break;	
					case VARENUM.VT_DATE:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = DateConverter.CDate2JDate(ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getDouble());
							offset[0] += 8;
						}
						break;	
					case VARENUM.VT_BOOL:
						for(i = 0; i < size; i++)
						{
							objTmp[i] = ByteBuffer.wrap(bin, offset[0], 2).order(BYTE_ENDIAN).getShort();
							if((Short) objTmp[i] == 0)
							{
								objTmp[i] = false;
							}
							else
							{
								objTmp[i] = true;
							}
							offset[0] += 2;
						}
						break;
					case VARENUM.VT_BSTR:
						int tmpLen;
						byte[] bTmp;
						for(i = 0; i < size; i++)
						{
							tmpLen = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getInt();
							bTmp = new byte[tmpLen];
							System.arraycopy(bin, offset[0] + 4, bTmp, 0, tmpLen);
							try{
								objTmp[i] = new String(bTmp, STRING_ENDIAN);
							}catch(UnsupportedEncodingException e){
								objTmp[i] = "";
							}
							offset[0] += (tmpLen + 4);
						}
						break;	
					case VARENUM.VT_VARIANT:
						objTmp = byte2VARIANTArray(bin, offset, size, false).toArray();
				}
				
				aryTmp = new SAFEARRAY(tmpVT, objTmp);
			}
			
			objRet = aryTmp;
		}
		else
		{
			switch(vt)
			{
				case VARENUM.VT_UI1:
					objRet = bin[offset[0]];
					offset[0]++;
					break;
				case VARENUM.VT_I2:
				case VARENUM.VT_UI2:
					objRet = ByteBuffer.wrap(bin, offset[0], 2).order(BYTE_ENDIAN).getShort();
					offset[0] += 2;
					break;
				case VARENUM.VT_ERROR:
				case VARENUM.VT_I4:
				case VARENUM.VT_UI4:
					objRet = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getInt();
					offset[0] += 4;
					break;
				case VARENUM.VT_R4:
					objRet = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getFloat();
					offset[0] += 4;
					break;
				case VARENUM.VT_R8:
					objRet = ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getDouble();
					offset[0] += 8;
					break;
				case VARENUM.VT_CY:
					objRet = ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getLong();
					offset[0] += 8;
					break;
				case VARENUM.VT_DATE:
					objRet = DateConverter.CDate2JDate(ByteBuffer.wrap(bin, offset[0], 8).order(BYTE_ENDIAN).getDouble());
					offset[0] += 8;
					break;
				case VARENUM.VT_BOOL:
					objRet = ByteBuffer.wrap(bin, offset[0], 2).order(BYTE_ENDIAN).getShort();
					if((Short) objRet == 0)
					{
						objRet = false;
					}
					else
					{
						objRet = true;
					}
					offset[0] += 2;
					break;
				case VARENUM.VT_BSTR:
					int tmpLen = ByteBuffer.wrap(bin, offset[0], 4).order(BYTE_ENDIAN).getInt();
					byte[] bTmp = new byte[tmpLen];
					System.arraycopy(bin, offset[0] + 4, bTmp, 0, tmpLen);
					try{
						objRet = new String(bTmp, STRING_ENDIAN);
					}catch(UnsupportedEncodingException e){
						objRet = "";
					}
					offset[0] += (tmpLen + 4);
					break;
			}
		}
		
		return objRet;
	}
	
	/**
	 * Encode bCapPacket to byte array
	 * 
	 * @param msg - bCapPacket to be encoded
	 * @return Byte array encoded from the msg
	 */
	public byte[] Encode(bCapPacket msg) throws Throwable
	{
		int size, num, offset[];
		byte[] bRet;
		VARIANT[] vntArray;
		
		// If msg is null, then throws NullPointerException
		if(msg == null){
			throw new NullPointerException();
		}
		
		// Calculate packet size
		size = BCAP_INFO_SIZE + m_szReserved + GetDataSize(msg);
		
		bRet = new byte[size];
		Arrays.fill(bRet, (byte)0);
		
		// SOH
		bRet[0] = 0x1;
		
		// Message Length
		ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[0], 4).order(BYTE_ENDIAN).putInt(size);
	
		// Serial Number
		ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[1], 2).order(BYTE_ENDIAN).putShort(msg.m_Serial);

		// Reservation Area
		ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[2], 2).order(BYTE_ENDIAN).putShort(msg.m_Reserv);
		
		// Function ID
		ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[3], 4).order(BYTE_ENDIAN).putInt(msg.m_ID);
		
		// Number of Arguments
		num = msg.m_vntArray.size();
		ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[4], 2).order(BYTE_ENDIAN).putShort((short)num);
		
		// Arguments
		vntArray = msg.m_vntArray.toArray(new VARIANT[]{});
		
		offset = new int[]{BCAP_INFO_OFFSET[5]};
		VARIANTArray2byte(vntArray, bRet, offset, true);
		
		// EOH
		bRet[bRet.length - 1] = 0x4;
		
		return bRet;
	}
	
	/**
	 * Encode VARIANT array to byte array
	 * 
	 * 	@param vntArray - VARIANT array to be encoded
	 * 	@param bout - Byte array encoded from the vntArray
	 * 	@param offset - Start point of the bout
	 *  @param first - Is this function called from Encode?
	 */
	private void VARIANTArray2byte(VARIANT[] vntArray, byte[] bout, int[] offset, boolean first) throws Throwable
	{
		int tmpVT, tmpSize, tmpPt;
		
		for(int i = 0; i < vntArray.length; i++)
		{
			tmpPt = offset[0];
			
			if(first){
				offset[0] += 4;
			}
			
			// Data Type
			tmpVT = vntArray[i].VariantGetType();
			ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((short)tmpVT);
			offset[0] += 2;

			// Number of Data
			if(((tmpVT & VARENUM.VT_ARRAY) != 0) && (vntArray[i].VariantGetObject() != null))
			{
				tmpSize = ((SAFEARRAY)(vntArray[i].VariantGetObject())).SafeArrayGetElemsize();
			}else{
				tmpSize = 1;
			}

			ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putInt(tmpSize);
			offset[0] += 4;

			// Argument
			VARIANT2byte(vntArray[i].VariantGetObject(), tmpVT, bout, offset);
			
			if(first){
				// Size of Argument
				ByteBuffer.wrap(bout, tmpPt, 4).order(BYTE_ENDIAN).putInt(offset[0] - tmpPt - 4);
			}
		}	
	}
	
	/**
	 * Encode VARIANT to byte array
	 * 
	 * @param objVal - VARIANT to be encoded
	 * @param vt - Data type
	 * @param bout - Byte array encoded from the objVal
	 * @param offset - Start point of the bout
	 */
	private void VARIANT2byte(Object objVal, int vt, byte[] bout, int[] offset) throws Throwable
	{
		if(objVal != null)
		{
			if((vt & VARENUM.VT_ARRAY) != 0)
			{
				SAFEARRAY aryTmp = (SAFEARRAY)objVal;
				
				int tmpVT = vt ^ VARENUM.VT_ARRAY;
				
				if(tmpVT == VARENUM.VT_UI1){
					byte[] bytArray = aryTmp.SafeArrayGetByteArray();
					System.arraycopy(bytArray, 0, bout, offset[0], bytArray.length);
					offset[0] += bytArray.length;
				}else{
					int i, tmpSize = aryTmp.SafeArrayGetElemsize();
					Object[] objTmp = aryTmp.SafeArrayGetArray();
					
					switch(tmpVT){
						case VARENUM.VT_I2:
						case VARENUM.VT_UI2:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((Short)objTmp[i]);
								offset[0] += 2;
							}
							break;
						case VARENUM.VT_I4:
						case VARENUM.VT_UI4:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putInt((Integer)objTmp[i]);
								offset[0] += 4;
							}
							break;
						case VARENUM.VT_R4:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putFloat((Float)objTmp[i]);
								offset[0] += 4;
							}
							break;
						case VARENUM.VT_R8:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putDouble((Double)objTmp[i]);
								offset[0] += 8;
							}
							break;
						case VARENUM.VT_CY:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putLong((Long)objTmp[i]);
								offset[0] += 8;
							}
							break;
						case VARENUM.VT_DATE:
							for(i = 0; i < tmpSize; i++){
								ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putDouble(DateConverter.JDate2CDate((java.util.Date)objTmp[i]));
								offset[0] += 8;
							}
							break;
						case VARENUM.VT_BOOL:
							for(i = 0; i < tmpSize; i++){
								if((Boolean)objTmp[i])
								{
									ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((short)-1);
								}
								else
								{
									ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((short)0);
								}
								offset[0] += 2;
							}
							break;
						case VARENUM.VT_BSTR:
							int tmpLen;
							byte[] bTmp;
							for(i = 0; i < tmpSize; i++){
								tmpLen = 2 * ((String)objTmp[i]).length();
								ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putInt(tmpLen);
								try {
									bTmp = ((String)objTmp[i]).getBytes(STRING_ENDIAN);
								} catch (UnsupportedEncodingException e) {
									bTmp = new byte[tmpLen];
								}
								System.arraycopy(bTmp, 0, bout, offset[0] + 4, tmpLen);
								offset[0] += (tmpLen + 4);
							}
							break;
						case VARENUM.VT_VARIANT:
							VARIANT[] vntTmp = Arrays.asList(objTmp).toArray(new VARIANT[]{});
							VARIANTArray2byte(vntTmp, bout, offset, false);
							break;
					}
				}
			}
			else
			{
				switch(vt)
				{
					case VARENUM.VT_UI1:
						bout[offset[0]] = (Byte)objVal;
						offset[0]++;
						break;
					case VARENUM.VT_I2:
					case VARENUM.VT_UI2:
						ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((Short)objVal);
						offset[0] += 2;
						break;
					case VARENUM.VT_ERROR:
					case VARENUM.VT_I4:
					case VARENUM.VT_UI4:
						ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putInt((Integer)objVal);
						offset[0] += 4;
						break;
					case VARENUM.VT_R4:
						ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putFloat((Float)objVal);
						offset[0] += 4;
						break;
					case VARENUM.VT_R8:
						ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putDouble((Double)objVal);
						offset[0] += 8;
						break;
					case VARENUM.VT_CY:
						ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putLong((Long)objVal);
						offset[0] += 8;
						break;
					case VARENUM.VT_DATE:
						ByteBuffer.wrap(bout, offset[0], 8).order(BYTE_ENDIAN).putDouble(DateConverter.JDate2CDate((java.util.Date)objVal));
						offset[0] += 8;
						break;
					case VARENUM.VT_BOOL:
						if((Boolean)objVal)
						{
							ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((short) -1);
						}
						else
						{
							ByteBuffer.wrap(bout, offset[0], 2).order(BYTE_ENDIAN).putShort((short) 0);
						}
						offset[0] += 2;
						break;
					case VARENUM.VT_BSTR:
						int tmpLen = 2 * ((String)objVal).length();
						ByteBuffer.wrap(bout, offset[0], 4).order(BYTE_ENDIAN).putInt(tmpLen);
						byte[] bTmp;
						try {
							bTmp = ((String)objVal).getBytes(STRING_ENDIAN);
						} catch (UnsupportedEncodingException e) {
							bTmp = new byte[tmpLen];
						}
						System.arraycopy(bTmp, 0, bout, offset[0] + 4, tmpLen);
						offset[0] += (tmpLen + 4);
						break;
				}
			}
		}
	}
	
	/**
	 *  Calculate the packet size of m_vntArray in msg
	 *  
	 *  @param msg - bCapPacket to be calculated the packet size
	 *  @return The packet size of m_vntArray in msg
	 */
	private int GetDataSize(bCapPacket msg)
	{
		int iRet = 0;
		VARIANT vntTmp;
		
		for(int i = 0; i < msg.m_vntArray.size(); i++)
		{
			vntTmp = msg.m_vntArray.get(i);
			iRet += (4 + 2 + 4 + VARIANT2Size(vntTmp.VariantGetType(), vntTmp.VariantGetObject()));
		}
		
		return iRet;
	}
	
	/**
	 *  Calculate the packet size of VARIANT
	 *  
	 * @param vt - Data type of the objVal
	 * @param objVal - VARIANT to be calculated the packet size 
	 * @return The packet size of objVal
	 */
	private int VARIANT2Size(int vt, Object objVal)
	{
		int iRet = 0;
		
		if((vt & VARENUM.VT_ARRAY) != 0)
		{
			if(objVal != null){
				SAFEARRAY ary = (SAFEARRAY) objVal;
				int iTmpNum = ary.SafeArrayGetElemsize();

				switch(vt ^ VARENUM.VT_ARRAY)
				{
					case VARENUM.VT_UI1:
						iRet = iTmpNum;
						break;
					case VARENUM.VT_I2:
					case VARENUM.VT_UI2:
					case VARENUM.VT_BOOL:
						iRet = iTmpNum * 2;
						break;
					case VARENUM.VT_I4:
					case VARENUM.VT_UI4:
					case VARENUM.VT_R4:
						iRet = iTmpNum * 4;
						break;
					case VARENUM.VT_R8:
					case VARENUM.VT_CY:
					case VARENUM.VT_DATE:
						iRet = iTmpNum * 8;
						break;
					case VARENUM.VT_BSTR:
						String str;
						for(int i = 0; i < iTmpNum; i++){
							str = (String) ary.SafeArrayGetElement(i);
							if(str != null){
								iRet += 4 + 2 * str.length();
							}else{
								iRet += 4;
							}
						}
						break;
					case VARENUM.VT_VARIANT:
						VARIANT vnt;
						for(int i = 0; i < iTmpNum; i++){
							vnt = (VARIANT) ary.SafeArrayGetElement(i);
							iRet += (2 + 4 + VARIANT2Size(vnt.VariantGetType(), vnt.VariantGetObject()));
						}
						break;
				}
			}
		}else{
			switch(vt)
			{
				case VARENUM.VT_UI1:
					iRet = 1;
					break;
				case VARENUM.VT_I2:
				case VARENUM.VT_UI2:
				case VARENUM.VT_BOOL:
					iRet = 2;
					break;
				case VARENUM.VT_ERROR:
				case VARENUM.VT_I4:
				case VARENUM.VT_UI4:
				case VARENUM.VT_R4:
					iRet = 4;
					break;
				case VARENUM.VT_R8:
				case VARENUM.VT_CY:
				case VARENUM.VT_DATE:
					iRet = 8;
					break;
				case VARENUM.VT_BSTR:
					if(objVal != null){
						iRet = 4 + 2 * ((String)objVal).length();
					}else{
						iRet = 4;
					}
					break;
			}
		}

		return iRet;
	}
	
	/**
	 * Calculate the packet size of byte array
	 * 
	 * @param msg - Byte array to be calculated the packet size
	 * @return The packet size calculated from msg
	 */
	public int MsgSize2Int(byte[] msg)
	{
		if(msg.length == 4){
			return ByteBuffer.wrap(msg, 0, 4).order(BYTE_ENDIAN).getInt();
		}
		else if(msg.length == 5){
			return ByteBuffer.wrap(msg, 1, 4).order(BYTE_ENDIAN).getInt();
		}
		else{
			return 0;
		}
	}

	/**
	 * Set serial number to the packet
	 * 
	 * @param msg - Byte array to set the serial number
	 * @param sSerial - Serial number
	 */
	public void SetSerial(byte[] msg, short sSerial)
	{
		// Serial Number
		ByteBuffer.wrap(msg, BCAP_INFO_OFFSET[1], 2).order(BYTE_ENDIAN).putShort(sSerial);
	}
	
	/**
	 * Calculate CRC value of the byte array
	 * 
	 * @param bin - Byte array to be calculated the CRC value
	 * @return The CRC value calculated from the byte array
	 */
	public int CalcCRC16(byte[] bin){
		int i, j, iResult = 0xFFFF;
		
		for(i = 0; i < bin.length; i++){
			iResult ^= (bin[i] << 8);
			for(j = 0; j < 8; j++){
				if((iResult & 0x8000) != 0){
					iResult <<= 1;
					iResult ^= CRC_CCITT16;
				}else{
					iResult <<= 1;
				}
			}
		}
		
		return iResult;
	}
	
	/**
	 * Uncompress the byte array
	 * 
	 * @param bin - Byte array to be uncompressed
	 * @return The byte array uncompressed from bin
	 */
	public byte[] ZipInflater(byte[] bin) throws Throwable{
		int iUnzipSize;
		byte[] bRet = null;
		Throwable thErr = null;
		
		try{
			iUnzipSize = ByteBuffer.wrap(bin, BCAP_INFO_OFFSET[3], 4).order(BYTE_ENDIAN).getInt();
			bRet = new byte[iUnzipSize + 11];
			
			System.arraycopy(bin, 0, bRet, 0, BCAP_INFO_OFFSET[3]);
			ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[0], 4).order(BYTE_ENDIAN).putInt(bRet.length);
			m_zipInflater.setInput(bin, BCAP_INFO_OFFSET[4], bin.length - 15);
			m_zipInflater.inflate(bRet, BCAP_INFO_OFFSET[3], iUnzipSize);
			
			bRet[bRet.length - 1] = 0x4;
		}catch(Throwable e){
			thErr = e;
		}
		finally{
			m_zipInflater.reset();
			
			if(thErr != null){
				throw thErr;
			}
		}
		
		return bRet;
	}
	
	/**
	 * Compress the byte array
	 * 
	 * @param bin - Byte array to be compressed
	 * @param ZipMode - The compress mode of ZIP
	 * @return The byte array compressed from bin
	 */
	public byte[] ZipDeflater(byte[] bin, int ZipMode) throws Throwable{
		int iUnZipSize, iZipSize;
		byte[] bRet = null, bZip = new byte[1024];
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		Throwable thErr = null;
		
		try{
			m_zipDeflater.setLevel(ZipMode);
			
			iUnZipSize = bin.length - 11;
			
			m_zipDeflater.setInput(bin, BCAP_INFO_OFFSET[3], iUnZipSize);
			m_zipDeflater.finish();
	
			while(!m_zipDeflater.finished()){
				iZipSize = m_zipDeflater.deflate(bZip);
				bos.write(bZip, 0, iZipSize);
			}

			iZipSize = bos.toByteArray().length;
			bRet = new byte[iZipSize + 15];
			
			System.arraycopy(bin, 0, bRet, 0, BCAP_INFO_OFFSET[3]);
			ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[0], 4).order(BYTE_ENDIAN).putInt(bRet.length);
			ByteBuffer.wrap(bRet, BCAP_INFO_OFFSET[3], 4).order(BYTE_ENDIAN).putInt(iUnZipSize);
			System.arraycopy(bos.toByteArray(), 0, bRet, BCAP_INFO_OFFSET[4], iZipSize);
			bRet[bRet.length - 2] = 0x1; // ZIP Mode
			bRet[bRet.length - 1] = 0x4; // EOH
		}catch(Throwable e){
			thErr = e;
		}finally{
			try{
				bos.close();
			}catch(IOException e){}
			
			m_zipDeflater.reset();
			
			if(thErr != null){
				throw thErr;
			}
		}
		
		return bRet;
	}
}
