package org.flashtool.flashsystem;


import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.flashtool.util.BytesUtil;
import org.flashtool.util.HexDump;


public class S1Packet {

	//[DWORD]  CMD
	//[DWORD]  FLAGS ( 1 | 2 | 4 )
	//[DWORD]  LEN
	//[BYTE]   HDR CHECKSUM
	//[BYTE[LEN]]  DATA
	//[DWORD]  DATA CHECKSUM (CRC32)
	int command=0;
	int flag=0;
	int length=0;
	byte hdrcksum=0;
	
	ByteArrayOutputStream bcommand = new ByteArrayOutputStream();
	ByteArrayOutputStream bflag = new ByteArrayOutputStream();
	ByteArrayOutputStream blength = new ByteArrayOutputStream();
	ByteArrayOutputStream bdata = new ByteArrayOutputStream();
	ByteArrayOutputStream bcrc32 = new ByteArrayOutputStream();

	byte[] data=null;
	byte[] crc32=null;
	

	public S1Packet(byte[] pdata) throws IOException {
		addData(pdata);
	}

	public S1Packet(int pcommand, byte[] pdata, boolean ongoing) {
		command = pcommand;
		setFlags(false,true,ongoing);
		if (pdata==null) 
			length = 0;
		else
			length = pdata.length;
		data=pdata;
		hdrcksum = calculateHeaderCkSum();
		crc32=calculatedCRC32();
	}

	public S1Packet(int pcommand, byte pdata, boolean ongoing) {
		command = pcommand;
		setFlags(false,true,ongoing);
		data = new byte[] {pdata};
		length=1;
		hdrcksum = calculateHeaderCkSum();
		crc32=calculatedCRC32();
	}

	public void mergeWith(S1Packet p) throws IOException {
		data=BytesUtil.concatAll(data, p.getDataArray());
		length=data.length;
		bdata.write(p.getDataArray());
		flag=p.getFlags();
		crc32=getCRC32();
	}
	
	public boolean isValid() {
		if (BytesUtil.getLong(calculatedCRC32())!=BytesUtil.getLong(crc32))
			return false;
		if (calculateHeaderCkSum()!=hdrcksum)
			return false;
		return true;
	}
	
	public void validate() throws X10FlashException {
		try {
			if (BytesUtil.getLong(calculatedCRC32())!=BytesUtil.getLong(crc32))
				throw new X10FlashException("S1 Data CRC32 Error");
			if (calculateHeaderCkSum()!=hdrcksum)
				throw new X10FlashException("S1 Header checksum Error");
		}
		catch (Exception e) {
			throw new X10FlashException(e.getMessage());
		}
	}
	
	public byte[] getByteArray() {
		if (length==0)
			return BytesUtil.concatAll(getHeader(), new byte[] {hdrcksum}, crc32);
		else
			return BytesUtil.concatAll(getHeader(), new byte[] {hdrcksum}, data, crc32);
	}

	public void release() {
		data = null;
		crc32 = null;
	}

	public void setFlags(boolean flag1, boolean flag2, boolean ongoing) {
		flag = getFlag(flag1,flag2,ongoing);
	}

	private int getFlag(boolean flag1, boolean flag2, boolean ongoing)
    {
        boolean flag = !flag1;
        byte byte0 = (byte)(flag2 ? 2 : 0);
        byte byte1 = (byte)(ongoing ? 4 : 0);
        return (((byte)(flag ? 1 : 0))) | byte0 | byte1;
    }

	public int getFlags() {
		 return flag;
	}

	public String getFlagsAsString() {
		String result = "";
		int flag1 = getFlags()&1;
		int flag2 = getFlags()&2;
		int multipacket = getFlags()&4;
		if (flag1==0) result = "true"; else result="false";
		if (flag2==0) result += ",false"; else result+=",true";
		if (multipacket==0) result += ",false"; else result+=",true";
		return result;
	}

	public boolean isMultiPacket() {
		int multipacket = getFlags()&4;
		if (multipacket==0) 
			return false; 
		else 
			return true;
	}
	
	public boolean hasErrors() {
		int flag1 = getFlags()&1;
		if (flag1==0) 
			return true; 
		else 
			return false;
	}

	public int getCommand() {
		return command;
	}

	public int getDataLength() {
		return length;
	}
	
	public byte[] getDataArray() {
		return data;
	}

	public String getDataString() {
		try {
			return new String(data);
		} catch (Exception e) { return "";}
	}

	public void addData(byte[] datachunk) throws IOException  {
		for (int i=0;i<datachunk.length;i++) {
			if (bcommand.toByteArray().length<4) {
				bcommand.write(datachunk[i]);
				if (command==0 && bcommand.toByteArray().length==4) {
					command=BytesUtil.getInt(bcommand.toByteArray());
				}
			}
			else if (bflag.toByteArray().length<4) {
				bflag.write(datachunk[i]);
				if (flag==0 && bflag.toByteArray().length==4) {
					flag=BytesUtil.getInt(bflag.toByteArray());
				}
			}
			else if (blength.toByteArray().length<4) {
				blength.write(datachunk[i]);
				if (length==0 && blength.toByteArray().length==4) {
					length=BytesUtil.getInt(blength.toByteArray());
				}
			}
			else if (hdrcksum==0) {
				hdrcksum=datachunk[i];
			}
			else if (bdata.toByteArray().length<length) {
				bdata.write(datachunk[i]);
				if (data==null && bdata.toByteArray().length==length) {
					data=bdata.toByteArray();
				}
			}
			else if (bcrc32.toByteArray().length<4) {
				bcrc32.write(datachunk[i]);
				if (crc32==null && bcrc32.toByteArray().length==4) {
					crc32=bcrc32.toByteArray();
				}
			}
		}
	}

	public String toString() { 	
	    return "CommandID : "+getCommand()+" / Flags : "+this.getFlagsAsString()+" / Data length : "+this.getDataLength()+" / Data CRC32 : "+HexDump.toHex(crc32);
	}

	public byte[] calculatedCRC32() {
		if (data ==null) return null;
		return BytesUtil.getCRC32(data);
	}

	public boolean isHeaderValid() {
		if (command==0) return false;
		return hdrcksum==calculateHeaderCkSum();
	}

	public boolean isDataComplete() {
		if (data==null && length==0) return true;
		if (data==null) return false;
		if (data.length<length) return false;
		return true;
	}

	public boolean isCRCComplete() {
		if (crc32==null) return false;
		if (crc32.length<4) return false;
		return true;
	}

	public boolean hasMoreToRead() {
		return !(isHeaderValid() && isDataComplete() && isCRCComplete());
	}

	public byte calculateHeaderCkSum()
    {
        byte header[] = getHeader();
        byte result = calcSum(header);
        header = null;
        return result;
    }

	private byte calcSum(byte paramArray[])
    {
        byte byte0 = 0;
        if(paramArray.length < 12)
            return 0;
        for(int i = 0; i < 12; i++)
            byte0 ^= paramArray[i];

        byte0 += 7;
        return byte0;
    }
	
	public void saveDataAs(String file) {
		try {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(data);
			fos.close();
		}
		catch (Exception e) {e.printStackTrace();}
	}
	
	public byte[] getCRC32() {
		return crc32;
	}
	
	public byte[] getHeader() {
		return BytesUtil.concatAll(BytesUtil.getBytesWord(command, 4),
				                   BytesUtil.getBytesWord(flag, 4),
				                   BytesUtil.getBytesWord(length, 4)
				                  );
	}

	public byte[] getHeaderWithChecksum() {
		return BytesUtil.concatAll(BytesUtil.getBytesWord(command, 4),
				                   BytesUtil.getBytesWord(flag, 4),
				                   BytesUtil.getBytesWord(length, 4),
				                   new byte[] {hdrcksum}
				                  );
	}

	public byte getCksum() {
		return hdrcksum;
	}

}