/** pomelo协议数据包编解码 */

export default () => {

    return new class {

        constructor() {
            /** 封装类型：握手 */
            this.TYPE_HANDSHAKE = 1;
            /** 封装类型：握手命令正确应答 */
            this.TYPE_HANDSHAKE_ACK = 2;
            /** 封装类型：心跳 */
            this.TYPE_HEARTBEAT = 3;
            /** 封装类型：数据 */
            this.TYPE_DATA = 4;
            /** 封装类型：服务器主动断开连接通知 */
            this.TYPE_KICK = 5;
            /** 封装消息的头的字节长度 */
            this.PKG_HEAD_BYTES = 4;
        };

        /**
         * 协议数据包编码
         * 
         *   类型   消息体长度           消息体
         * +------+-------------+------------------+
         * | type | body length |       body       |
         * +------+-------------+------------------+
         *
         * Head: 4bytes
         *   0: package type,  --0包类型
         *      1 - handshake,        --1：握手
         *      2 - handshake ack,    --2：握手的回复
         *      3 - heartbeat,        --3：心跳
         *      4 - data              --4：数据
         *      5 - kick              --5：服务器主动断开连接通知
         *   1 - 3: big-endian body length 消息体字节长度，高位优先
         * Body: body length bytes 消息体字节长度
         * @param  {Number}      数据包的类型
         * @param  {Uint8Array}  消息体的字节数据
         * @return {Uint8Array}  编码好的包含字节数组的数据
         */
        encode(type, body) {
            // 消息体的字节数组长度
            var length = body ? body.length : 0;
            // 创建buffer字节数组，长度为定义的封装的数据头部长度+消息体的长度
            var buffer = new Uint8Array(this.PKG_HEAD_BYTES + length);
            // 开始处理消息的头部，这里长度做了唯一运算，头部的4个字节，高8位用于指定操作类型，低24位3个字节用于指定消息内容的长度
            var index = 0;

            // 包体采用3个字节24位的数字来表示数据的长度
            buffer[index++] = type & 0xff;              // 去掉符号位
            buffer[index++] = (length >> 16) & 0xff;    // 取第二个字节的数值，并去掉运算符
            buffer[index++] = (length >> 8) & 0xff;     // 取第三个字节的数值，并去掉运算符
            buffer[index++] = length & 0xff;            // 获取低8位（第四个字节）的值，并去掉符号位

            // 将消息数据体复制到buffer
            if (body) {
                this.copyArray(buffer, index, body, 0, length);
            };

            return buffer;
        };

        /**
         * 协议数据包解码
         * @param  {Uint8Array} buffer 包含包内容的字节数组
         * @return {{type: package type, buffer: body byte array}} 返回协议对象 
         */
        decode(buffer) {
            // 偏移
            var offset = 0;
            // 数据
            var bytes = new Uint8Array(buffer);
            // 数据长度
            var length = 0;
            // 初始返回的数据
            var rs = [];

            while (offset < bytes.length) {
                // 获取消息的类型
                var type = bytes[offset++];
                // 计算消息的长度，头的1-3个字节
                length = ((bytes[offset++]) << 16 | (bytes[offset++]) << 8 | bytes[offset++]) >>> 0;
                // 用来承载消息内容的字节数组
                var body = length ? new Uint8Array(length) : null;
                this.copyArray(body, 0, bytes, offset, length);
                offset += length;
                rs.push({ 'type': type, 'body': body });
            };

            return rs.length === 1 ? rs[0] : rs;
        };

        /**
         * 克隆字节数组
         * @param {Array} dest 目标数组-需要复制数据到的目标数组
         * @param {Number} doffset 目标数组偏移量
         * @param {Array} src 源数组-源数据的数组
         * @param {Number} soffset 源数组偏移量
         * @param {Number} length 要复制的源数组数据长度
         */
        copyArray(dest, doffset, src, soffset, length) {
            if ('function' === typeof src.copy) {
                // Buffer
                src.copy(dest, doffset, soffset, soffset + length);
            } else {
                // Uint8Array
                for (var index = 0; index < length; index++) {
                    dest[doffset++] = src[soffset++];
                };
            };
        };
    };
}