/** Google的数据压缩算法 */

export default () => {

	class Codec {

		constructor() {
			/** 字节缓存 */
			this.buffer = new ArrayBuffer(8);
			/** 32位浮点数字节数组 */
			this.float32Array = new Float32Array(this.buffer);
			/** 64位浮点数字节数组 */
			this.float64Array = new Float64Array(this.buffer);
			/** utf8编码字节数组 */
			this.uInt8Array = new Uint8Array(this.buffer);
		};

		/**
		 * 将数字编码为32位无符号整数
		 * @param {Number} num 
		 */
		encodeUInt32(num) {
			var n = parseInt(num);
			if (isNaN(n) || n < 0) {
				return null;
			};

			var result = [];
			do {
				var tmp = n % 128;
				var next = Math.floor(n / 128);

				if (next !== 0) {
					tmp = tmp + 128;
				};
				result.push(tmp);
				n = next;
			} while (n !== 0);

			return result;
		};

		/**
		 * 将数字编码为short
		 * @param {Number} num 
		 */
		encodeSInt32(num) {
			var n = parseInt(num);
			if (isNaN(n)) {
				return null;
			};
			n = n < 0 ? (Math.abs(n) * 2 - 1) : n * 2;

			return this.encodeUInt32(n);
		};

		/**
		 * 将字节数组转码为无符号32位整数
		 * @param {Array<Number>} bytes 
		 */
		decodeUInt32(bytes) {
			var n = 0;

			for (var i = 0; i < bytes.length; i++) {
				var m = parseInt(bytes[i]);
				n = n + ((m & 0x7f) * Math.pow(2, (7 * i)));
				if (m < 128) {
					return n;
				};
			};

			return n;
		};

		/**
		 * 将字节数组转码为short
		 * @param {Array<Number>} bytes 
		 */
		decodeSInt32(bytes) {
			var n = this.decodeUInt32(bytes);
			var flag = ((n % 2) === 1) ? -1 : 1;

			n = ((n % 2 + n) / 2) * flag;

			return n;
		};

		/**
		 * 将浮点数转码
		 * @param {Number} float 
		 */
		encodeFloat(float) {
			this.float32Array[0] = float;
			return this.uInt8Array;
		};

		/**
		 * 将字节数组转换浮点数
		 * @param {any} bytes 
		 * @param {any} offset 
		 */
		decodeFloat(bytes, offset) {
			if (!bytes || bytes.length < (offset + 4)) {
				return null;
			};

			for (var i = 0; i < 4; i++) {
				this.uInt8Array[i] = bytes[offset + i];
			};

			return this.float32Array[0];
		};

		/**
		 * 将double编码
		 * @param {Number} double 
		 */
		encodeDouble(double) {
			this.float64Array[0] = double;
			return this.uInt8Array.subarray(0, 8);
		};

		/**
		 * 将double解码
		 * @param {any} bytes 
		 * @param {any} offset 
		 */
		decodeDouble(bytes, offset) {
			if (!bytes || bytes.length < (8 + offset)) {
				return null;
			};

			for (var i = 0; i < 8; i++) {
				this.uInt8Array[i] = bytes[offset + i];
			};

			return this.float64Array[0];
		};

		/**
		 * 字符串编码
		 * @param {any} bytes 
		 * @param {any} offset 
		 * @param {any} str 
		 */
		encodeStr(bytes, offset, str) {
			for (var i = 0; i < str.length; i++) {
				var code = str.charCodeAt(i);
				var codes = this.encode2UTF8(code);

				for (var j = 0; j < codes.length; j++) {
					bytes[offset] = codes[j];
					offset++;
				};
			};

			return offset;
		};

		/**
		 * 字节数组转字符串
		 * 
		 * @param {any} bytes 
		 * @param {any} offset 
		 * @param {any} length 
		 */
		decodeStr(bytes, offset, length) {
			var array = [];
			var end = offset + length;

			while (offset < end) {
				var code = 0;

				if (bytes[offset] < 128) {
					code = bytes[offset];

					offset += 1;
				} else if (bytes[offset] < 224) {
					code = ((bytes[offset] & 0x3f) << 6) + (bytes[offset + 1] & 0x3f);
					offset += 2;
				} else {
					code = ((bytes[offset] & 0x0f) << 12) + ((bytes[offset + 1] & 0x3f) << 6) + (bytes[offset + 2] & 0x3f);
					offset += 3;
				};

				array.push(code);
			};

			var str = '';
			for (var i = 0; i < array.length;) {
				str += String.fromCharCode.apply(null, array.slice(i, i + 10000));
				i += 10000;
			};

			return str;
		};

		/**
		 * 返回采用utf8编码的字符串的字节长度
		 * @param {String} str 
		 */
		byteLength(str) {
			if (typeof (str) !== 'string') {
				return -1;
			};

			var length = 0;

			for (var i = 0; i < str.length; i++) {
				var code = str.charCodeAt(i);
				length += this.codeLength(code);
			};

			return length;
		};

		/**
		 * 将unicode16字符编码转为utf8字节
		 * @param {any} charCode 
		 */
		encode2UTF8(charCode) {
			if (charCode <= 0x7f) {
				return [charCode];
			} else if (charCode <= 0x7ff) {
				return [0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f)];
			} else {
				return [0xe0 | (charCode >> 12), 0x80 | ((charCode & 0xfc0) >> 6), 0x80 | (charCode & 0x3f)];
			};
		};

		/**
		 * 编码长度
		 * 
		 * @param {any} code 
		 * @returns 
		 */
		codeLength(code) {
			if (code <= 0x7f) {
				return 1;
			} else if (code <= 0x7ff) {
				return 2;
			} else {
				return 3;
			};
		};
	};

	class Encoder {

		constructor(codec, constant, util) {
			this.codec = codec;
			this.constant = constant;
			this.util = util;
		};

		init(protos) {
			this.protos = protos || {};
		};

		/**
		 * 编码
		 * @param {any} route 
		 * @param {any} msg 
		 */
		encode(route, msg) {
			/** 使用路由作为key从协议映射中获取对应的协议 */
			var protos = this.protos[route];

			if (!this.checkMsg(msg, protos)) {
				return null;
			};

			// 将消息转为json格式的字符串，并计算其作为uint8的字节数组的长度
			var length = this.codec.byteLength(JSON.stringify(msg));

			// 初始化buffer和偏移量
			var buffer = new ArrayBuffer(length);
			var uInt8Array = new Uint8Array(buffer);
			var offset = 0;

			if (!!protos) {
				offset = encodeMsg(uInt8Array, offset, protos, msg);
				if (offset > 0) {
					return uInt8Array.subarray(0, offset);
				};
			};

			return null;
		};

		/**
		 * 按照协议的定义校验消息数据
		 * @param {any} msg 
		 * @param {any} protos 
		 */
		checkMsg(msg, protos) {
			if (!protos) {
				return false;
			};

			for (var name in protos) {
				var proto = protos[name];

				// required一定得存在
				switch (proto.option) {
					case 'required':
						if (typeof (msg[name]) === 'undefined') {
							return false;
						};
					case 'optional':
						if (typeof (msg[name]) !== 'undefined') {
							var message = protos.__messages[proto.type] || protos['message ' + proto.type];
							if (!!message && !checkMsg(msg[name], message)) {
								return false;
							};
						};
						break;
					case 'repeated':
						var message = protos.__messages[proto.type] || protos['message ' + proto.type];
						if (!!msg[name] && !!message) {
							for (var i = 0; i < msg[name].length; i++) {
								if (!checkMsg(msg[name][i], message)) {
									return false;
								};
							};
						};
						break;
				};
			};

			return true;
		};

		/**
		 * 消息编码
		 * @param {any} buffer 
		 * @param {any} offset 
		 * @param {any} protos 
		 * @param {any} msg 
		 */
		encodeMsg(buffer, offset, protos, msg) {
			for (var name in msg) {
				if (!!protos[name]) {
					var proto = protos[name];

					switch (proto.option) {
						case 'required':
						case 'optional':
							offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag));
							offset = this.encodeProp(msg[name], proto.type, offset, buffer, protos);
							break;
						case 'repeated':
							if (msg[name].length > 0) {
								offset = this.encodeArray(msg[name], proto, offset, buffer, protos);
							};
							break;
					};
				};
			};

			return offset;
		};

		/**
		 * 属性编码
		 * @param {any} value 
		 * @param {any} type 
		 * @param {any} offset 
		 * @param {any} buffer 
		 * @param {any} protos 
		 */
		encodeProp(value, type, offset, buffer, protos) {
			switch (type) {
				case 'uInt32':
					offset = this.writeBytes(buffer, offset, this.codec.encodeUInt32(value));
					break;
				case 'int32':
				case 'sInt32':
					offset = this.writeBytes(buffer, offset, this.codec.encodeSInt32(value));
					break;
				case 'float':
					writeBytes(buffer, offset, this.codec.encodeFloat(value));
					offset += 4;
					break;
				case 'double':
					writeBytes(buffer, offset, this.codec.encodeDouble(value));
					offset += 8;
					break;
				case 'string':
					var length = this.codec.byteLength(value);

					// 编码长度
					offset = this.writeBytes(buffer, offset, this.codec.encodeUInt32(length));
					// 输出编码内容
					this.codec.encodeStr(buffer, offset, value);
					offset += length;
					break;
				default:
					var message = protos.__messages[type] || protos['message ' + type];
					if (!!message) {
						// 使用临时缓冲区构建内部消息
						var tmpBuffer = new ArrayBuffer(this.codec.byteLength(JSON.stringify(value)) * 2);
						var length = 0;

						length = this.encodeMsg(tmpBuffer, length, message, value);
						// 编码长度
						offset = this.writeBytes(buffer, offset, this.codec.encodeUInt32(length));
						// 赋值对象
						for (var i = 0; i < length; i++) {
							buffer[offset] = tmpBuffer[i];
							offset++;
						};
					};
					break;
			};

			return offset;
		};


		/**
		 * 数组编码
		 * @param {any} array 
		 * @param {any} proto 
		 * @param {any} offset 
		 * @param {any} buffer 
		 * @param {any} protos 
		 */
		encodeArray(array, proto, offset, buffer, protos) {
			var i = 0;

			if (this.util.isSimpleType(proto.type)) {
				offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag));
				offset = this.writeBytes(buffer, offset, this.codec.encodeUInt32(array.length));
				for (i = 0; i < array.length; i++) {
					offset = this.encodeProp(array[i], proto.type, offset, buffer);
				};
			} else {
				for (i = 0; i < array.length; i++) {
					offset = this.writeBytes(buffer, offset, this.encodeTag(proto.type, proto.tag));
					offset = this.encodeProp(array[i], proto.type, offset, buffer, protos);
				};
			};

			return offset;
		};

		/**
		 * 写入字节数组
		 * @param {any} buffer 
		 * @param {any} offset 
		 * @param {any} bytes 
		 */
		writeBytes(buffer, offset, bytes) {
			for (var i = 0; i < bytes.length; i++ , offset++) {
				buffer[offset] = bytes[i];
			};

			return offset;
		};

		/**
		 * 编码标签
		 * @param {any} type 
		 * @param {any} tag 
		 */
		encodeTag(type, tag) {
			var value = constant.TYPES[type] || 2;
			return this.codec.encodeUInt32((tag << 3) | value);
		};
	};

	class Decoder {

		constructor(codec, util) {
			/** 缓冲容器 */
			this.buffer;
			/** 偏移量 */
			this.offset = 0;
			/** protobuf */
			this.protos = null;
			this.codec = codec;
			this.util = util;
		};

		init(protos) {
			this.protos = protos || {};
		};

		/**
		 * 设置协议
		 * @param {any} protos 
		 */
		setProtos(protos) {
			if (!!protos) {
				this.protos = protos;
			};
		};

		/**
		 * 解码
		 * @param {any} route 
		 * @param {any} buf 
		 */
		decode(route, buf) {
			var protos = this.protos[route];

			this.buffer = buf;
			this.offset = 0;

			if (!!protos) {
				return this.decodeMsg({}, protos, this.buffer.length);
			};

			return null;
		};

		/**
		 * 解码消息
		 * @param {any} msg 
		 * @param {any} protos 
		 * @param {any} length 
		 */
		decodeMsg(msg, protos, length) {
			while (this.offset < length) {
				var head = this.getHead();
				var type = head.type;
				var tag = head.tag;
				var name = protos.__tags[tag];

				switch (protos[name].option) {
					case 'optional':
					case 'required':
						msg[name] = this.decodeProp(protos[name].type, protos);
						break;
					case 'repeated':
						if (!msg[name]) {
							msg[name] = [];
						};
						this.decodeArray(msg[name], protos[name].type, protos);
						break;
				};
			};

			return msg;
		};

		/**
		 * 测试给定的消息是否结束
		 * @param {any} msg 
		 * @param {any} protos 
		 */
		isFinish(msg, protos) {
			return (!protos.__tags[this.peekHead().tag]);
		};

		/** 获取protobuf的头属性 */
		getHead() {
			var tag = this.codec.decodeUInt32(this.getBytes());

			return {
				type: tag & 0x7,
				tag: tag >> 3
			};
		};

		/** 在不移动偏移量的情况下获取标记头 */
		peekHead() {
			var tag = this.codec.decodeUInt32(this.peekBytes());

			return {
				type: tag & 0x7,
				tag: tag >> 3
			};
		};

		/**
		 * 解码属性
		 * @param {any} type 
		 * @param {any} protos 
		 */
		decodeProp(type, protos) {
			switch (type) {
				case 'uInt32':
					return this.codec.decodeUInt32(this.getBytes());
				case 'int32':
				case 'sInt32':
					return this.codec.decodeSInt32(this.getBytes());
				case 'float':
					var float = this.codec.decodeFloat(this.buffer, this.offset);
					this.offset += 4;
					return float;
				case 'double':
					var double = this.codec.decodeDouble(this.buffer, this.offset);
					this.offset += 8;
					return double;
				case 'string':
					var length = this.codec.decodeUInt32(this.getBytes());

					var str = this.codec.decodeStr(this.buffer, this.offset, length);
					this.offset += length;

					return str;
				default:
					var message = protos && (protos.__messages[type] || protos['message ' + type]);
					if (!!message) {
						var length = this.codec.decodeUInt32(this.getBytes());
						var msg = {};
						this.decodeMsg(msg, message, this.offset + length);
						return msg;
					};
					break;
			};
		};

		/**
		 * 解码数组字节数组
		 * @param {any} array 
		 * @param {any} type 
		 * @param {any} protos 
		 */
		decodeArray(array, type, protos) {

			if (this.util.isSimpleType(type)) {
				var length = this.codec.decodeUInt32(this.getBytes());

				for (var i = 0; i < length; i++) {
					array.push(this.decodeProp(type));
				};
			} else {
				array.push(this.decodeProp(type, protos));
			};
		};

		/** 获取字节数 */
		getBytes(flag) {
			var bytes = [];
			var pos = this.offset;
			flag = flag || false;

			var b;

			do {
				b = this.buffer[pos];
				bytes.push(b);
				pos++;
			} while (b >= 128);

			if (!flag) {
				this.offset = pos;
			};
			return bytes;
		};

		/** 检索字节 */
		peekBytes() {
			return this.getBytes(true);
		};
	};

	return new class {

		constructor() {
			/** 常量 */
			this.constant = {
				/** 数据类型 */
				TYPES: {
					uInt32: 0,
					sInt32: 0,
					int32: 0,
					double: 1,
					string: 2,
					message: 2,
					float: 5
				}
			};

			/** 工具 */
			this.util = {
				/**
				 * 是否是基础类型
				 * @param {String} type 类型
				 * @return {Boolean}
				 */
				isSimpleType(type) {
					return (type === 'uInt32' ||
						type === 'sInt32' ||
						type === 'int32' ||
						type === 'uInt64' ||
						type === 'sInt64' ||
						type === 'float' ||
						type === 'double');
				}
			};

			// 实例
			this.codec = new Codec();
			this.encoder = new Encoder(this.codec, this.constant, this.util);
			this.decoder = new Decoder(this.codec, this.util);
		};

		/** 初始化protobuf */
		init(opts) {
			this.encoder.init(opts.encoderProtos);
			this.decoder.init(opts.decoderProtos);
		};

		/**
		 * 编码
		 * @param {any} key 
		 * @param {any} msg 
		 */
		encode(key, msg) {
			return this.encoder.encode(key, msg);
		};

		/**
		 * 解码
		 * @param {any} key 
		 * @param {any} msg 
		 */
		decode(key, msg) {
			return this.decoder.decode(key, msg);
		};
	};
}