package cn.ac.caict.iiiiot.id.client.convertor;
import cn.ac.caict.iiiiot.id.client.core.AddValueRequest;
import cn.ac.caict.iiiiot.id.client.core.BaseMessage;
import cn.ac.caict.iiiiot.id.client.core.ChallengeAnswerRequest;
import cn.ac.caict.iiiiot.id.client.core.CreateIdentifierRequest;
import cn.ac.caict.iiiiot.id.client.core.DeleteIdentifierRequest;
import cn.ac.caict.iiiiot.id.client.core.IdentifierException;
import cn.ac.caict.iiiiot.id.client.core.LoginIDSystemRequest;
import cn.ac.caict.iiiiot.id.client.core.LoginRequest;
import cn.ac.caict.iiiiot.id.client.core.ModifyValueRequest;
import cn.ac.caict.iiiiot.id.client.core.MsgEnvelope;
import cn.ac.caict.iiiiot.id.client.core.RemoveValueRequest;
import cn.ac.caict.iiiiot.id.client.core.ResolutionRequest;
import cn.ac.caict.iiiiot.id.client.core.SiteRequest;
import cn.ac.caict.iiiiot.id.client.data.IdentifierValue;
import cn.ac.caict.iiiiot.id.client.utils.Common;
import cn.ac.caict.iiiiot.id.client.utils.ExceptionCommon;
import cn.ac.caict.iiiiot.id.client.utils.MessageCommon;

public abstract class MsgBytesConvertor extends BaseConvertor {
	public static final byte[] messageConvertIntoBytes(BaseMessage msg) throws IdentifierException {
		byte[] msg_buf = null;
		if (msg.opCode == MessageCommon.OC_RESOLUTION)
			msg_buf = convertResolutionReqToBytes((ResolutionRequest) msg);
		else if (msg.opCode == MessageCommon.OC_LOGIN)
			msg_buf = convertLoginReqToBytes((LoginRequest) msg);
		else if (msg.opCode == MessageCommon.OC_LOGIN_ID_SYSTEM)
			msg_buf = convertLoginIDSystemReqToBytes((LoginIDSystemRequest) msg);
		else if (msg.opCode == MessageCommon.OC_CREATE_IDENTIFIER)
			msg_buf = convertCreateIdentifierReqToBytes((CreateIdentifierRequest) msg);
		else if (msg.opCode == MessageCommon.OC_DELETE_IDENTIFIER)
			msg_buf = convertDeleteIdentifierReqToBytes((DeleteIdentifierRequest) msg);
		else if (msg.opCode == MessageCommon.OC_RESPONSE_TO_CHALLENGE)
			msg_buf = convertChallengeAnswerReqToBytes((ChallengeAnswerRequest) msg);
		else if (msg.opCode == MessageCommon.OC_ADD_VALUE)
			msg_buf = convertAddValueReqToBytes((AddValueRequest) msg);
		else if (msg.opCode == MessageCommon.OC_MODIFY_VALUE)
			msg_buf = convertModifyValueReqToBytes((ModifyValueRequest) msg);
		else if (msg.opCode == MessageCommon.OC_REMOVE_VALUE)
			msg_buf = convertRemoveValueReqToBytes((RemoveValueRequest) msg);
		else if(msg.opCode == MessageCommon.OC_GET_SITE){
			msg_buf = convertSiteReqToBytes((SiteRequest) msg);
		}
		else
			throw new IdentifierException(ExceptionCommon.EXCEPTIONCODE_INTERNAL_ERROR, "未知操作: " + msg.opCode);
		if (msg_buf == null)
			throw new IdentifierException(ExceptionCommon.EXCEPTIONCODE_INTERNAL_ERROR,
					msg.opCode + "该请求操作没有实现将消息转换为字节!");
		return msg_buf;
	}
	public static void envelopeConvertInfoBytes(MsgEnvelope msgEnv, byte[] udpPkt) throws IdentifierException {
		//共20字节
		udpPkt[0] = msgEnv.protocolMajorVersion;
		udpPkt[1] = msgEnv.protocolMinorVersion;
		// 压缩、加密、截断、主要协议版本
		byte byteCompressed = msgEnv.compressed ? Common.ENV_FLAG_COMPRESSED : 0;
		byte byteEncrypted = msgEnv.encrypted ? Common.ENV_FLAG_ENCRYPTED : 0;
		byte byteTruncated = msgEnv.truncated ? Common.ENV_FLAG_TRUNCATED : 0;
		udpPkt[2] = (byte) (byteCompressed | byteEncrypted | byteTruncated | msgEnv.suggestMajorProtocolVersion);
		// 建议次要协议版本
		udpPkt[3] = msgEnv.suggestMinorProtocolVersion;
		write4Bytes(udpPkt, 4, msgEnv.sessionId); // bytes 4,5,6,7
		write4Bytes(udpPkt, 8, msgEnv.requestId); // bytes 8,9,10,11
		write4Bytes(udpPkt, 12, msgEnv.messageSequenceNum); // bytes 12,13,14,15
		write4Bytes(udpPkt, 16, msgEnv.messageLength); // bytes 16,17,18,19
	}
	public static int convertIdentifierValueToByte(byte[] buf, int offset, IdentifierValue value) {
		if(value == null)
			return 0;
		int origOffset = offset;
		write4Bytes(buf, offset, value.index);
		offset += Common.FOUR_SIZE;
		write4Bytes(buf, offset, (int) value.timestamp);
		offset += Common.FOUR_SIZE;
		buf[offset++] = value.ttlType;
		write4Bytes(buf, offset, value.ttl);
		offset += Common.FOUR_SIZE;
		byte byteAdminRead = value.bAdminRead ? Common.PERM_ADMIN_READ : 0;
		byte byteAdminWrite = value.bAdminWrite ? Common.PERM_ADMIN_WRITE : 0;
		byte bytePublicRead = value.bPublicRead ? Common.PERM_PUBLIC_READ : 0;
		byte bytePublicWrite = value.bPublicWrite ? Common.PERM_PUBLIC_WRITE : 0;
		buf[offset++] = (byte) (byteAdminRead | byteAdminWrite | bytePublicRead | bytePublicWrite);
		offset += writeByteArray(buf, offset, value.type, 0, value.type.length);
		offset += writeByteArray(buf, offset, value.data, 0, value.data.length);
		if (value.references != null) {
			offset += write4Bytes(buf, offset, value.references.length);
			for (int i = 0; i < value.references.length; i++) {
				offset += writeByteArray(buf, offset, value.references[i].identifier);
				offset += write4Bytes(buf, offset, value.references[i].index);
			}
		} else {
			offset += write4Bytes(buf, offset, 0);
		}
		return offset - origOffset;
	}
	
	public static byte[] convertIdentifierValueToByte(IdentifierValue value) {
		byte[] buf = new byte[calcStorageSize(value)];
		convertIdentifierValueToByte(buf, 0, value);
		return buf;
	}
	
	public static byte[] convertSiteReqToBytes(SiteRequest req) {
		// TODO Auto-generated method stub
		int idSize = req.identifier.length;
		int idLen = Common.FOUR_SIZE;
		byte[] msg = new byte[Common.MESSAGE_HEADER_SIZE + idSize + idLen];
		int offset = writeHeader(req, msg, idSize + idLen);
		writeByteArray(msg,offset,req.identifier);
		return msg;
	}
	//////////////////////////////////////////////// private-founctions//////////////////////////////////////////////////////
	private static byte[] convertResolutionReqToBytes(ResolutionRequest req) {
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE
				+ ((req.requestedIndexes == null) ? 0 : req.requestedIndexes.length * Common.FOUR_SIZE)
				+ Common.FOUR_SIZE;
		if (req.requestedTypes != null) {
			for (int i = 0; i < req.requestedTypes.length; i++) {
				bodyLen += (req.requestedTypes[i].length + Common.FOUR_SIZE);
			}
		}
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		writeHeader(req, msg, bodyLen);
		int pos = Common.MESSAGE_HEADER_SIZE;
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		pos += writeIntArray(msg, pos, req.requestedIndexes);
		pos += writeByteArrayArray(msg, pos, req.requestedTypes);
		return msg;
	}

	private static byte[] convertLoginReqToBytes(LoginRequest req) {
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE
				+ ((req.requestedIndexes == null) ? 0 : req.requestedIndexes.length * Common.FOUR_SIZE)
				+ Common.FOUR_SIZE;
		if (req.requestedTypes != null) {
			for (int i = 0; i < req.requestedTypes.length; i++) {
				bodyLen += (req.requestedTypes[i].length + Common.FOUR_SIZE);
			}
		}
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		writeHeader(req, msg, bodyLen);
		int pos = Common.MESSAGE_HEADER_SIZE;
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		pos += writeIntArray(msg, pos, req.requestedIndexes);
		pos += writeByteArrayArray(msg, pos, req.requestedTypes);
		return msg;
	}

	private static byte[] convertRemoveValueReqToBytes(RemoveValueRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE
				+ req.indexes.length * Common.FOUR_SIZE;
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		int pos = writeHeader(req, msg, bodyLen);
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		pos += writeIntArray(msg, pos, req.indexes);
		return msg;
	}

	private static byte[] convertModifyValueReqToBytes(ModifyValueRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE;
		for (int i = 0; i < req.values.length; i++) {
			bodyLen += calcStorageSize(req.values[i]);
		}
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		int pos = writeHeader(req, msg, bodyLen);
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		pos += write4Bytes(msg, pos, req.values.length);
		for (int i = 0; i < req.values.length; i++)
			pos += convertIdentifierValueToByte(msg, pos, req.values[i]);
		return msg;
	}

	private static byte[] convertAddValueReqToBytes(AddValueRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE;

		for (int i = 0; i < req.values.length; i++)
			bodyLen += calcStorageSize(req.values[i]);
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		int pos = writeHeader(req, msg, bodyLen);
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		pos += write4Bytes(msg, pos, req.values.length);
		for (int i = 0; i < req.values.length; i++)
			pos += convertIdentifierValueToByte(msg, pos, req.values[i]);
		return msg;
	}

	private static byte[] convertChallengeAnswerReqToBytes(ChallengeAnswerRequest req) {
		// TODO Auto-generated method stub
		byte[] msg = new byte[Common.MESSAGE_HEADER_SIZE + Common.FOUR_SIZE + req.authType.length + Common.FOUR_SIZE
				+ req.userIdIdentifier.length + Common.FOUR_SIZE + Common.FOUR_SIZE + req.signedResponse.length];
		int offset = writeHeader(req, msg, msg.length - Common.MESSAGE_HEADER_SIZE);
		offset += writeByteArray(msg, offset, req.authType, 0, req.authType.length);
		offset += writeByteArray(msg, offset, req.userIdIdentifier, 0, req.userIdIdentifier.length);
		offset += write4Bytes(msg, offset, req.userIdIndex);
		offset += writeByteArray(msg, offset, req.signedResponse, 0, req.signedResponse.length);
		return msg;
	}

	private static byte[] convertDeleteIdentifierReqToBytes(DeleteIdentifierRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = Common.FOUR_SIZE + req.identifier.length;
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		int loc = writeHeader(req, msg, bodyLen);
		loc += writeByteArray(msg, loc, req.identifier, 0, req.identifier.length);
		return msg;
	}

	private static byte[] convertCreateIdentifierReqToBytes(CreateIdentifierRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = Common.FOUR_SIZE + req.identifier.length + Common.FOUR_SIZE;
		if (req.values != null && req.values.length > 0) {
			for (int i = 0; i < req.values.length; i++)
				bodyLen += calcStorageSize(req.values[i]);
		}
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		int pos = writeHeader(req, msg, bodyLen);
		pos += writeByteArray(msg, pos, req.identifier, 0, req.identifier.length);
		if (req.values != null && req.values.length > 0) {
			pos += write4Bytes(msg, pos, req.values.length);
			for (int i = 0; i < req.values.length; i++) {
				pos += convertIdentifierValueToByte(msg, pos, req.values[i]);
			}
		} else {
			pos += write4Bytes(msg, pos, 0);
		}
		return msg;
	}

	private static byte[] convertLoginIDSystemReqToBytes(LoginIDSystemRequest req) {
		// TODO Auto-generated method stub
		int bodyLen = req.identifier.length + Common.FOUR_SIZE * 2;
		byte[] msg = new byte[bodyLen + Common.MESSAGE_HEADER_SIZE];
		writeHeader(req, msg, bodyLen);
		int loc = Common.MESSAGE_HEADER_SIZE;
		loc += writeByteArray(msg, loc, req.identifier, 0, req.identifier.length);
		loc += write4Bytes(msg, loc, req.requestedIndexes);
		return msg;
	}

	private static final int writeHeader(BaseMessage msg, byte[] buf, int bodyLen) {
		int offset = 0;
		offset += write4Bytes(buf, offset, msg.opCode);
		offset += write4Bytes(buf, offset, msg.responseCode);
		int flags = 0;
		if (msg.bAuthoritative)
			flags |= Common.FLAG_1ST_MSG_AUTH;
		if (msg.bCertify)
			flags |= Common.FLAG_2ND_MSG_CERT;
		if (msg.bEncrypt)
			flags |= Common.FLAG_3RD_MSG_ENCR;
		if (msg.bRecursive)
			flags |= Common.FLAG_4TH_MSG_RECU;
		if (msg.bCacheCertify)
			flags |= Common.FLAG_5TH_MSG_CACR;
		if (msg.continuous)
			flags |= Common.FLAG_6TH_MSG_CONT;
		if (msg.bKeepAlive)
			flags |= Common.FLAG_7TH_MSG_KPAL;
		if (msg.ignoreRestrictedValues)
			flags |= Common.FLAG_8TH_MSG_PUBL;
		if (msg.returnRequestDigest)
			flags |= Common.FLAG_9TH_MSG_RRDG;
		if (msg.overwriteWhenExists)
			flags |= Common.FLAG_10TH_MSG_OVRW;
		if (msg.mintNewSuffix)
			flags |= Common.FLAG_11TH_MSG_MINT;
		if (msg.doNotRefer)
			flags |= Common.FLAG_12TH_MSG_DNRF;
		if (msg.trustedQuery)
			flags |= Common.FLAG_31ST_MSG_TRUSTED_QUERY;
		offset += write4Bytes(buf, offset, flags);
		offset += write2Bytes(buf, offset, msg.siteInfoSerial);
		buf[offset++] = (byte) msg.recursionCount;
		offset++;
		offset += write4Bytes(buf, offset, msg.expiration);
		offset += write4Bytes(buf, offset, bodyLen);
		return Common.MESSAGE_HEADER_SIZE;
	}
}