package com.ee.ctp.ftdc;

import com.ee.ctp.enums.FtdType;
import com.ee.ctp.enums.FtdcType;
import com.ee.ctp.enums.Sequence;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
/**
 * 
 * 格式:|--type--|--ext length--|--body length--|---ext body---|---body-----|</br>
 * 
 * 长度:|--1-----|-- 1 ---------|--2------------|-- 0~127------|--0~65535---|
 * 
 * @author ee
 * 2017年10月17日 下午8:27:04
 *
 */
public class FtdcProtocol {
	private static final int MAX_COMPRESS_SIZE_ONCE = 0x0f;
	private FtdType type;
	private int extLength;
	private int bodyLength;
	private ByteBuf extBuf;
	private ByteBuf bodyBuf;
	
	private Ext ext;
	private Ftdc ftdc;
	@SuppressWarnings("unused")
	private FtdcProtocol() { 
		// nop
	}
	
	/**
	 * 用于构造编码协议,特用于登录
	 * @param type
	 * @param ftdcBody
	 * @param ftdcType
	 * @param reqId
	 * @param tid
	 */
	public FtdcProtocol(FtdType type, ByteBuf ftdcBody, int ftdcType, int reqId, int tid) {
		this.type = type;
		this.ftdc = new Ftdc(ftdcBody, ftdcType, reqId, tid);
		this.bodyLength = ftdc.len();
	}
	/**
	 * 用于构造编码协议
	 * @param type
	 * @param ftdcBody
	 * @param ftdcType 协议类型:REQ或者RSP,根据此类型的不同,sequence不同
	 * @param reqId
	 * @param tid
	 * @param sequence
	 */
	public FtdcProtocol(FtdType type, ByteBuf ftdcBody, int ftdcType, int reqId, int tid, Sequence sequence) {
		this.type = type;
		this.ftdc = new Ftdc(ftdcBody, ftdcType, reqId, tid, sequence);
		this.bodyLength = ftdc.len();
	}
	
	/**
	 * 用于构造心跳
	 * @param type
	 */
	public FtdcProtocol(FtdType type) {
		this.type = type;
	}
	
	/**
	 * 用于构造解析协议
	 * @param byteType
	 * @param extLength
	 * @param bodyLength
	 * @param buf
	 * @param compress
	 */
	public FtdcProtocol(short byteType, short extLength, int bodyLength, ByteBuf buf, boolean compress) {
		this.type = FtdType.parseFrom(byteType);
		this.extLength = extLength;
		this.bodyLength = bodyLength;
		extractBodyAndExt(extLength, bodyLength, buf, compress);
	}

	public void write(ByteBuf buffer) {
		try {
			int length = extLength;
			ByteBuf extBuffer = Unpooled.buffer();
			while(length > 0) {
				extBuffer.writeByte(ext.getTagType());
				extBuffer.writeByte(ext.getTagLength());
				switch(ext.getTagLength()) {
				case 1:
					extBuffer.writeByte(ext.getTagValue());
					break;
				case 2:
					extBuffer.writeShort(ext.getTagValue());
					break;
				case 4:
					extBuffer.writeInt(ext.getTagValue());
					break;
				default:
					break;
				}
				length = length - 2 - ext.getTagLength();
				ext = ext.next();
			}
			
			buffer.writeByte(this.type.type());
			buffer.writeByte(extLength);
			ByteBuf ftdcBytes = null;
			if(ftdc != null) {
				ftdcBytes = this.ftdc.write();
				if(FtdcType.RSP.type() == this.ftdc.type) {
					ftdcBytes = compress(ftdcBytes);
				}
				buffer.writeShort(ftdcBytes.readableBytes());
			}else {
				buffer.writeShort(0);
			}
			buffer.writeBytes(extBuffer);
			if(ftdcBytes != null) {
				buffer.writeBytes(ftdcBytes);
			}
		} finally {
			ReferenceCountUtil.release(buffer);
		}
	}
	
	private void extractBodyAndExt(int extLength, int bodyLength, ByteBuf buf, boolean compress) {
		try{
			if(hasExt()) {
				extBuf = buf.retainedSlice(buf.readerIndex(), extLength);
				buf.skipBytes(extLength);
				readExt();
			}
			if(hasBody()) {
				bodyBuf = buf.retainedSlice(buf.readerIndex(), bodyLength);
				buf.skipBytes(bodyLength);
				if(compress) {
					readFtdc();
				}else {
					readFtdcUnUnCompress();
				}
			}
		}finally {
			ReferenceCountUtil.release(buf);
		}
	}
	
	public void addExt(int tagType, int tagLength, int tagValue) {
		ext = new Ext(tagType, tagLength, tagValue, ext);
		extLength = extLength + 2 + tagLength;
	}
	
	private void releaseExtBuf() {
		if(hasExt()) {
			ReferenceCountUtil.release(extBuf);
		}
	}
	
	private void releaseFtdcBuf() {
		if(hasBody()) {
			ReferenceCountUtil.release(bodyBuf);
		}
	}
	
	private void readExt() {
		try{
			int length = extLength;
			while(length > 0) {
				int tagType = extBuf.readByte();
				int tagLength = extBuf.readUnsignedByte();
				int tagValue;
				switch(tagLength) {
				case 1:
					tagValue = extBuf.readByte();
					break;
				case 2:
					tagValue = extBuf.readShort();
					break;
				case 4:
					tagValue = extBuf.readInt();
					break;
				default:
					tagValue = 0;
				}
				ext = new Ext(tagType, tagLength, tagValue, ext);
				length = length - 1 - 1 - tagLength;
			}
		} finally {
			releaseExtBuf();
		}
	}
	
	
	private void readFtdc() {
		try{
			ByteBuf buffer = uncompress();
			ftdc = new Ftdc(buffer);
		} finally {
			releaseFtdcBuf();
		}
	}
	
	private void readFtdcUnUnCompress() {
		try{
			ftdc = new Ftdc(bodyBuf);
		} finally {
			releaseFtdcBuf();
		}
	}

	private ByteBuf uncompress() {
		ByteBuf buffer = Unpooled.buffer();
		for(;bodyBuf.readerIndex() < bodyBuf.writerIndex();) {
			short b = bodyBuf.readUnsignedByte();
			if((b >> 4) != 14) {
				buffer.writeByte(b);
			}else {
				int size = b & 15;
				if(size == 0) {
					buffer.writeByte(bodyBuf.readByte());
				} else {
					buffer.writeZero(size);
				}
			}
		}
		return buffer;
	}
	
	private ByteBuf compress(ByteBuf buf) {
		ByteBuf compressedBuffer = Unpooled.buffer();
		int size = 0;
		boolean isZero = false;
		for(;buf.readerIndex() < buf.writerIndex();) {
			short temp = buf.readUnsignedByte();
			if(temp != 0) {
				if(isZero) {
					compressedBuffer.writeByte(0xe0 + (size & 0xff));
					size = 0;
					isZero = false;	
				}
				
				if((temp >> 4) == 14) {
					compressedBuffer.writeByte(0xe0);
				}
				compressedBuffer.writeByte(temp);
				
			}else {
				size ++;
				if(size == MAX_COMPRESS_SIZE_ONCE) {
					compressedBuffer.writeByte(0xef);
					size = 0;
					isZero = false;
				}else {
					isZero = true;
				}
				
			}
		}
		if(isZero && size > 0) {
			compressedBuffer.writeByte(0xe0 + (size & 0xff));
		}
		return compressedBuffer;
	}
	
	public Ftdc ftdc() {
		return ftdc;
	}
	
	public Ext ext() {
		return ext;
	}
	
	public FtdType getType() {
		return type;
	}
	
	public void setType(FtdType type) {
		this.type = type;
	}
	
	public int getExtLength() {
		return extLength;
	}
	
	public void setExtLength(int extLength) {
		this.extLength = extLength;
	}
	
	public int getBodyLength() {
		return bodyLength;
	}
	
	public void setBodyLength(int bodyLength) {
		this.bodyLength = bodyLength;
	}
	
	public boolean hasExt() {
		return extLength > 0;
	}
	
	public boolean hasBody() {
		return bodyLength > 0;
	}
	
	/**
	 * 总体格式|-------------------------------------------------ftdc head----------------------------------------------------|------ftdc body-------|<br/>
	 * 头部格式|--version--|--type--|--ul--|--chain--|--stype--|--sequence--|--cs--|--num--|--len--|--reqid--|--tid--|--tlen--|------ftdc body-------|<br/>
	 * 长度大小|-----1-----|----1---|---1--|----1----|----2----|------4-----|---4--|---2---|----2--|-----4---|---2---|----2---|------0~65535---------|
	 * @author ee
	 * 2017年11月10日 下午5:41:06
	 *
	 */
	public static class Ftdc {
		private static final int FTDC_HEAD_LEN = 22;
		//1
		private int version = 1;
		//1
		private int type;
		//1
		private int unencodeLength = 0x0c;
		//1
		private int chain = 0x4c;
		//2
		private int sequenceType = 0;
		//4
		private int sequence = 0x00003000;
		//4
		private int currentSequence = 0x1;
		//2
		private int numData = 0x1;
		//2
		private int ftdcLen;
		//4
		private long reqId;
		//4 前两个字节是业务TID,后两个是业务结构体长度
		private long tid;
		
		private ByteBuf ftdcBody;
		
		public Ftdc(ByteBuf ftdcBody, int type, int reqId, int tid, Sequence sequence) {
			this.ftdcBody = ftdcBody;
			this.type = type;
			this.reqId = reqId;
			this.tid = tid;
			this.ftdcLen = ftdcBody.readableBytes();
			if(tid != 0) {
				this.ftdcLen = ftdcLen + 4;
			}
			if(FtdcType.REQ.type() == type) {
				this.sequence = sequence.sequence();
			}else {
				this.sequence = sequence.rspSequence();
			}
			this.sequenceType = sequence.sequenceType();
		}
		
		public Ftdc(ByteBuf ftdcBody, int type, int reqId, int tid) {
			this(ftdcBody, type, reqId, tid, Sequence.UserLogin);
		}
		
		public int len() {
			return this.ftdcLen + FTDC_HEAD_LEN;
		}
		
		public boolean hasBody() {
			return len() > FTDC_HEAD_LEN;
		}
		
		public ByteBuf write() {
			try {
				ByteBuf buffer = Unpooled.buffer();
				buffer.writeByte(this.version);
				buffer.writeByte(this.type);
				buffer.writeByte(this.unencodeLength);
				buffer.writeByte(this.chain);
				buffer.writeShort(this.sequenceType);
				buffer.writeInt(this.sequence);
				buffer.writeInt(this.currentSequence);
				buffer.writeShort(this.numData);
				buffer.writeShort(this.ftdcLen);
				buffer.writeInt((int)this.reqId);
				buffer.writeInt((int)this.getTid());
				buffer.writeBytes(ftdcBody);
				return buffer;
			} finally {
				release();
			}
			
		}
		
		public Ftdc(ByteBuf body) {
			this.version = body.readByte();
			this.type = body.readByte();
			this.unencodeLength = body.readByte();
			this.chain = body.readByte();
			this.sequenceType = body.readShort();
			this.sequence = body.readInt();
			this.currentSequence = body.readInt();
			this.numData = body.readShort();
			this.ftdcLen = body.readUnsignedShort();
			this.reqId = body.readUnsignedInt();
			//没有数据的情况是不存在tid的
			if(ftdcLen != 0) {
				this.tid = body.readUnsignedInt();
				int actualLen = this.ftdcLen - 4;
				if(body.readableBytes() < actualLen) {
					throw new IllegalArgumentException("bad body length");
				}
				ftdcBody = body.retainedSlice(body.readerIndex(), actualLen);
			}
			
		}
		
		public ByteBuf ftdcBody() {
			return ftdcBody;
		}
		
		public void release() {
			while(ftdcBody != null && ftdcBody.refCnt() > 0) {
				ReferenceCountUtil.release(ftdcBody);
			}
		}

		public int getVersion() {
			return version;
		}

		public void setVersion(int version) {
			this.version = version;
		}

		public int getType() {
			return type;
		}

		public void setType(int type) {
			this.type = type;
		}

		public int getUnencodeLength() {
			return unencodeLength;
		}

		public void setUnencodeLength(int unencodeLength) {
			this.unencodeLength = unencodeLength;
		}

		public int getChain() {
			return chain;
		}

		public void setChain(int chain) {
			this.chain = chain;
		}

		public int getSequenceType() {
			return sequenceType;
		}

		public void setSequenceType(int sequenceType) {
			this.sequenceType = sequenceType;
		}

		public int getSequence() {
			return sequence;
		}

		public void setSequence(int sequence) {
			this.sequence = sequence;
		}

		public int getCurrentSequence() {
			return currentSequence;
		}

		public void setCurrentSequence(int currentSequence) {
			this.currentSequence = currentSequence;
		}

		public int getNumData() {
			return numData;
		}

		public void setNumData(int numData) {
			this.numData = numData;
		}

		public int getFtdcLen() {
			return ftdcLen;
		}

		public void setFtdcLen(int ftdcLen) {
			this.ftdcLen = ftdcLen;
		}

		public long getReqId() {
			return reqId;
		}

		public void setReqId(long reqId) {
			this.reqId = reqId;
		}

		public long getTid() {
			return tid;
		}

		public void setTid(long tid) {
			this.tid = tid;
		}
		
		public int getStructOfTidLen() {
			return (int)(this.tid & 0xffff);
		}

		@Override
		public String toString() {
			return "Ftdc [version=" + version + ", type=" + type + ", unencodeLength=" + unencodeLength + ", chain="
					+ chain + ", sequenceType=" + sequenceType + ", sequence=" + sequence + ", unknown=" + currentSequence
					+ ", numData=" + numData + ", ftdcLen=" + ftdcLen + ", reqId=" + reqId + ", tid=" + tid + "]";
		}
		
		
	}
	
	/**
	 * 格式: |---tagType---|---tagLength---|---tagValue---| </br>
	 * 长度: |-----1-------|------1--------|--0~255-------|
	 * @author ee
	 * 2017年11月10日 下午5:36:45
	 *
	 */
	public static class Ext {
		private int tagType;
		private int tagLength;
		private int tagValue;
		private Ext next;
		public Ext(int tagType, int tagLength, int tagValue, Ext next) {
			this.tagType = tagType;
			this.tagLength = tagLength;
			this.tagValue = tagValue;
			this.next = next;
		}
		public int getTagType() {
			return tagType;
		}
		public void setTagType(int tagType) {
			this.tagType = tagType;
		}
		public int getTagLength() {
			return tagLength;
		}
		public void setTagLength(int tagLength) {
			this.tagLength = tagLength;
		}
		public int getTagValue() {
			return tagValue;
		}
		public void setTagValue(int tagValue) {
			this.tagValue = tagValue;
		}
		
		public Ext next() {
			return next;
		}
		@Override
		public String toString() {
			return "Ext [tagType=" + tagType + ", tagLength=" + tagLength + ", tagValue=" + tagValue + "]";
		}
	}
}