/** pomelo消息编解码 */

import Protocol from './Protocol.js';

export default () => {

    return new class {

        constructor() {
            /** 协议数据编码/解码工具 */
            this.protocol = Protocol();

            /** 消息标志的字节长度 */
            this.MSG_FLAG_BYTES = 1;
            /** 路由编码的字节长度 */
            this.MSG_ROUTE_CODE_BYTES = 2;
            /** 消息id的最大字节数 */
            this.MSG_ID_MAX_BYTES = 5;
            /** 消息路由的长度字节数 */
            this.MSG_ROUTE_LEN_BYTES = 1;
            /** 消息路由编码的最大长度 */
            this.MSG_ROUTE_CODE_MAX = 0xffff;
            /** 消息路由压缩掩码 */
            this.MSG_COMPRESS_ROUTE_MASK = 0x1;
            /** 消息类型掩码 */
            this.MSG_TYPE_MASK = 0x7;
            /** 消息类型：请求，以回调的方式接受服务端的响应 */
            this.TYPE_REQUEST = 0;
            /** 消息类型：通知，发送给服务端，不需要响应 */
            this.TYPE_NOTIFY = 1;
            /** 消息类型：响应，从服务端接收的 */
            this.TYPE_RESPONSE = 2;
            /** 消息类型：推送，服务端主动推送给客户端的消息 */
            this.TYPE_PUSH = 3;
        };

        /**
         * 消息协议编码
         * @param  {Number} id 消息ID
         * @param  {Number} type 消息的类型
         * @param  {Number} compressRoute 是否压缩路由
         * @param  {Number} route 路由
         * @param  {Uint8Array} msg 消息体的字节数据
         */
        encode(id, type, compressRoute, route, msg) {
            // 消息最大长度
            var idBytes = this.msgHasId(type) ? this.caculateMsgIdBytes(id) : 0;
            var msgLen = this.MSG_FLAG_BYTES + idBytes;

            // 校验是否是请求、通知、推送
            if (this.msgHasRoute(type)) {
                if (compressRoute) {
                    if (typeof route !== 'number') {
                        return console.error('路由参数错误!');
                    };
                    msgLen += this.MSG_ROUTE_CODE_BYTES;
                } else {
                    msgLen += this.MSG_ROUTE_LEN_BYTES;
                    if (route) {
                        // 编码路由
                        route = this.protocol.strencode(route);
                        if (route.length > 255) {
                            return console.error('路由超长！');
                        };
                        msgLen += route.length;
                    };
                };
            };

            if (msg) {
                msgLen += msg.length;
            };

            var buffer = new Uint8Array(msgLen);
            var offset = 0;

            // 添加消息标志到消息中
            offset = this.encodeMsgFlag(type, compressRoute, buffer, offset);

            // 添加消息id到消息中
            if (this.msgHasId(type)) {
                offset = this.encodeMsgId(id, buffer, offset);
            };

            // 添加路由到消息中
            if (this.msgHasRoute(type)) {
                offset = this.encodeMsgRoute(compressRoute, route, buffer, offset);
            };

            // 添加消息体
            if (msg) {
                offset = this.encodeMsgBody(msg, buffer, offset);
            };

            return buffer;
        };

        /**
         * 消息协议解码
         * @param  {Buffer|Uint8Array} buffer 消息数据
         */
        decode(buffer) {
            var bytes = new Uint8Array(buffer);
            var bytesLen = bytes.length || bytes.byteLength;
            var offset = 0;
            var id = 0;
            var route = null;

            // 解析消息标志
            var flag = bytes[offset++];
            var compressRoute = flag & this.MSG_COMPRESS_ROUTE_MASK;
            var type = (flag >> 1) & this.MSG_TYPE_MASK;

            // 解析消息id
            if (this.msgHasId(type)) {
                var m = parseInt(bytes[offset]);
                var i = 0;
                do {
                    var m = parseInt(bytes[offset]);
                    id = id + ((m & 0x7f) * Math.pow(2, (7 * i)));
                    offset++;
                    i++;
                } while (m >= 128);
            };

            // 解析路由
            if (this.msgHasRoute(type)) {
                if (compressRoute) {
                    route = (bytes[offset++]) << 8 | bytes[offset++];
                } else {
                    var routeLen = bytes[offset++];
                    if (routeLen) {
                        route = new Uint8Array(routeLen);
                        this.copyArray(route, 0, bytes, offset, routeLen);
                        route = this.protocol.strdecode(route);
                    } else {
                        route = '';
                    };
                    offset += routeLen;
                };
            };

            // 解析消息体
            var bodyLen = bytesLen - offset;
            var body = new Uint8Array(bodyLen);

            this.copyArray(body, 0, bytes, offset, bodyLen);

            return {
                'id': id,
                'type': type,
                'compressRoute': compressRoute,
                'route': route,
                'body': body
            };
        };

        /**
         * 是否是请求或者响应类型的消息
         * @param {Number} type 消息类型
         * @return {Boolean}
         */
        msgHasId(type) {
            return type === this.TYPE_REQUEST || type === this.TYPE_RESPONSE;
        };

        /**
         * 消息是否为请求、通知、推送之一的路由
         * @param {Number} type 
         * @return {Boolean} 
         */
        msgHasRoute(type) {
            return type === this.TYPE_REQUEST || type === this.TYPE_NOTIFY || type === this.TYPE_PUSH;
        };

        /**
         * 计算消息的id的字节长度
         * @param {Number} id 
         * @return {Number} 消息id的字节长度
         */
        caculateMsgIdBytes(id) {
            var len = 0;
            do {
                len += 1;
                id >>= 7;
            } while (id > 0);
            return len;
        };

        /**
         * 对消息标志进行编码
         * 
         * @param {Number} type 消息类型
         * @param {Number} compressRoute 是否压缩
         * @param {Uint8Array} buffer 消息数据
         * @param {Number} offset 偏移量
         */
        encodeMsgFlag(type, compressRoute, buffer, offset) {
            // 校验消息类型
            if (type !== this.TYPE_REQUEST && type !== this.TYPE_NOTIFY &&
                type !== this.TYPE_RESPONSE && type !== this.TYPE_PUSH) {

                return console.error('未知消息类型：' + type);
            };

            buffer[offset] = (type << 1) | (compressRoute ? 1 : 0);

            return offset + this.MSG_FLAG_BYTES;
        };

        /**
         * 对消息id进行编码
         * @param {Number} id 消息id
         * @param {Uint8Array} buffer 消息数据 
         * @param {Number} offset 偏移量
         */
        encodeMsgId(id, buffer, offset) {
            do {
                var tmp = id % 128;
                var next = Math.floor(id / 128);

                if (next !== 0) {
                    tmp = tmp + 128;
                };
                buffer[offset++] = tmp;

                id = next;
            } while (id !== 0);

            return offset;
        };

        /**
         * 对路由进行编码
         * @param {Number} compressRoute 是否压缩路由
         * @param {Number} route 路由
         * @param {Uint8Array} buffer 消息数据
         * @param {Number} offset 偏移量
         */
        encodeMsgRoute(compressRoute, route, buffer, offset) {
            if (compressRoute) {
                if (route > this.MSG_ROUTE_CODE_MAX) {
                    return console.error('消息超长');
                };

                buffer[offset++] = (route >> 8) & 0xff;
                buffer[offset++] = route & 0xff;
            } else {
                if (route) {
                    buffer[offset++] = route.length & 0xff;
                    this.copyArray(buffer, offset, route, 0, route.length);
                    offset += route.length;
                } else {
                    buffer[offset++] = 0;
                };
            };

            return offset;
        };

        /**
         * 对消息体进行编码
         * @param {Uint8Array} msg 消息体
         * @param {Uint8Array} buffer 消息数据
         * @param {Number} offset 偏移量
         */
        encodeMsgBody(msg, buffer, offset) {
            this.copyArray(buffer, offset, msg, 0, msg.length);
            return offset + msg.length;
        };

        /**
         * 克隆字节数组
         * @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++];
                };
            };
        };
    };
}