"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const egret = require("./ByteArray");
const WebSocket = require("ws");
var PinusWSClientEvent;
(function (PinusWSClientEvent) {
    PinusWSClientEvent["EVENT_IO_ERROR"] = "io-error";
    PinusWSClientEvent["EVENT_CLOSE"] = "close";
    PinusWSClientEvent["EVENT_KICK"] = "onKick";
    PinusWSClientEvent["EVENT_HEART_BEAT_TIMEOUT"] = "heartbeat timeout";
})(PinusWSClientEvent = exports.PinusWSClientEvent || (exports.PinusWSClientEvent = {}));
class PinusWSClient {
    constructor() {
        this.JS_WS_CLIENT_TYPE = 'js-websocket';
        this.JS_WS_CLIENT_VERSION = '0.0.5';
        this.RES_OK = 200;
        this.RES_FAIL = 500;
        this.RES_OLD_CLIENT = 501;
        this.socket = null;
        this.callbacks = {};
        this.handlers = {};
        // Map from request id to route
        this.routeMap = {};
        this.heartbeatInterval = 0;
        this.heartbeatTimeout = 0;
        this.nextHeartbeatTimeout = 0;
        this.gapThreshold = 100;
        this.heartbeatId = null;
        this.heartbeatTimeoutId = null;
        this.handshakeCallback = null;
        this.initCallback = null;
        this._callbacks = {};
        this.reqId = 0;
        this.socket = null;
        this.callbacks = {};
        this.handlers = {};
        // Map from request id to route
        this.routeMap = {};
        this._message = new Message(this.routeMap);
        this._package = new Package();
        this.heartbeatInterval = 0;
        this.heartbeatTimeout = 0;
        this.nextHeartbeatTimeout = 0;
        this.gapThreshold = 100;
        this.heartbeatId = null;
        this.heartbeatTimeoutId = null;
        this.handshakeCallback = null;
        this.handshakeBuffer = {
            'sys': {
                type: this.JS_WS_CLIENT_TYPE,
                version: this.JS_WS_CLIENT_VERSION
            },
            'user': {}
        };
        this.initCallback = null;
        this.reqId = 0;
        this.handlers[Package.TYPE_HANDSHAKE] = this.handshake;
        this.handlers[Package.TYPE_HEARTBEAT] = this.heartbeat;
        this.handlers[Package.TYPE_DATA] = this.onData;
        this.handlers[Package.TYPE_KICK] = this.onKick;
    }
    init(params, cb) {
        if (PinusWSClient.DEBUG) {
            console.log('init', params);
        }
        this.initCallback = cb;
        let host = params.host;
        let port = params.port;
        //
        // var url = 'ws://' + host;
        // if(port) {
        //    url +=  ':' + port;
        // }
        this.handshakeBuffer.user = params.user;
        this.handshakeCallback = params.handshakeCallback;
        this.initWebSocket(host, port, cb);
    }
    initWebSocket(host, port, cb) {
        if (PinusWSClient.DEBUG) {
            console.log('[Pinus] connect to:', host, port);
        }
        let url = 'ws://' + host;
        if (port) {
            url += ':' + port;
        }
        let socket = new WebSocket(url);
        socket.binaryType = 'arraybuffer';
        socket.onopen = (event) => {
            this.onConnect();
        };
        socket.onmessage = (event) => {
            this.onMessage(event);
        };
        socket.onerror = (event) => {
            this.onIOError(event);
        };
        socket.onclose = (event) => {
            this.onClose(event);
        };
        this.socket = socket;
    }
    on(event, fn) {
        (this._callbacks[event] = this._callbacks[event] || []).push(fn);
    }
    request(route, msg, cb) {
        if (arguments.length === 2 && typeof msg === 'function') {
            cb = msg;
            msg = {};
        }
        else {
            msg = msg || {};
        }
        route = route || msg.route;
        if (!route) {
            return;
        }
        this.reqId++;
        if (this.reqId > 127) {
            this.reqId = 1;
        }
        let reqId = this.reqId;
        if (PinusWSClient.DEBUG) {
            console.log(`REQUEST:route:${route} , reqId:${reqId}, msg:${msg}`);
        }
        this.sendMessage(reqId, route, msg);
        this.callbacks[reqId] = cb;
        this.routeMap[reqId] = route;
    }
    notify(route, msg) {
        this.sendMessage(0, route, msg);
    }
    onMessage(event) {
        this.processPackage(this._package.decode(new egret.ByteArray(event.data)));
    }
    sendMessage(reqId, route, msg) {
        let byte;
        byte = this._message.encode(reqId, route, msg);
        byte = this._package.encode(Package.TYPE_DATA, byte);
        this.send(byte);
    }
    onConnect() {
        if (PinusWSClient.DEBUG) {
            console.log('[Pinus] connect success');
        }
        this.send(this._package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(this.handshakeBuffer))));
    }
    onClose(e) {
        if (PinusWSClient.DEBUG) {
            console.error('[Pinus] connect close');
        }
        // this.emit(Pinus.EVENT_CLOSE,e);
    }
    onIOError(e) {
        // this.emit(Pinus.EVENT_IO_ERROR, e);
        console.error('socket error: ', e);
    }
    onKick(event) {
        // this.emit(PinusWSClient.EVENT_KICK,event);
    }
    onData(data) {
        // probuff decode
        let msg = this._message.decode(data);
        if (msg.id > 0) {
            msg.route = this.routeMap[msg.id];
            delete this.routeMap[msg.id];
            if (!msg.route) {
                return;
            }
        }
        // msg.body = this.deCompose(msg);
        this.processMessage(msg);
    }
    processMessage(msg) {
        if (!msg.id) {
            // server push message
            if (PinusWSClient.DEBUG) {
                console.log(`EVENT: Route:${msg.route} Msg:${msg.body}`);
            }
            this.emit(msg.route, msg.body);
            return;
        }
        if (PinusWSClient.DEBUG) {
            console.log(`RESPONSE: Id:${msg.id} Msg:${msg.body}`);
        }
        // if have a id then find the callback function with the request
        let cb = this.callbacks[msg.id];
        delete this.callbacks[msg.id];
        if (typeof cb !== 'function') {
            return;
        }
        if (msg.body && msg.body.code === 500) {
            let obj = { 'code': 500, 'desc': '服务器内部错误', 'key': 'INTERNAL_ERROR' };
            msg.body.error = obj;
        }
        cb(msg.body);
        return;
    }
    heartbeat(data) {
        if (!this.heartbeatInterval) {
            // no heartbeat
            return;
        }
        let obj = this._package.encode(Package.TYPE_HEARTBEAT);
        if (this.heartbeatTimeoutId) {
            clearTimeout(this.heartbeatTimeoutId);
            this.heartbeatTimeoutId = null;
        }
        if (this.heartbeatId) {
            // already in a heartbeat interval
            return;
        }
        let self = this;
        self.heartbeatId = setTimeout(function () {
            self.heartbeatId = null;
            self.send(obj);
            self.nextHeartbeatTimeout = Date.now() + self.heartbeatTimeout;
            self.heartbeatTimeoutId = setTimeout(self.heartbeatTimeoutCb.bind(self, data), self.heartbeatTimeout);
        }, self.heartbeatInterval);
    }
    heartbeatTimeoutCb(data) {
        let gap = this.nextHeartbeatTimeout - Date.now();
        if (gap > this.gapThreshold) {
            this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb.bind(this, data), gap);
        }
        else {
            console.error('server heartbeat timeout', data);
            // this.emit(PinusWSClient.EVENT_HEART_BEAT_TIMEOUT,data);
            this._disconnect();
        }
    }
    off(event, fn) {
        this.removeAllListeners(event, fn);
    }
    removeAllListeners(event, fn) {
        // all
        if (0 === arguments.length) {
            this._callbacks = {};
            return;
        }
        // specific event
        let callbacks = this._callbacks[event];
        if (!callbacks) {
            return;
        }
        // remove all handlers
        if (event && !fn) {
            delete this._callbacks[event];
            return;
        }
        // remove specific handler
        let i = this.index(callbacks, fn._off || fn);
        if (~i) {
            callbacks.splice(i, 1);
        }
        return;
    }
    index(arr, obj) {
        if ([].indexOf) {
            return arr.indexOf(obj);
        }
        for (let i = 0; i < arr.length; ++i) {
            if (arr[i] === obj)
                return i;
        }
        return -1;
    }
    disconnect() {
        this._disconnect();
    }
    _disconnect() {
        if (PinusWSClient.DEBUG) {
            console.warn('[Pinus] client disconnect ...');
        }
        if (this.socket)
            this.socket.close();
        this.socket = null;
        if (this.heartbeatId) {
            clearTimeout(this.heartbeatId);
            this.heartbeatId = null;
        }
        if (this.heartbeatTimeoutId) {
            clearTimeout(this.heartbeatTimeoutId);
            this.heartbeatTimeoutId = null;
        }
    }
    processPackage(msg) {
        this.handlers[msg.type].apply(this, [msg.body]);
    }
    handshake(resData) {
        let data = JSON.parse(Protocol.strdecode(resData));
        if (data.code === this.RES_OLD_CLIENT) {
            // this.emit(PinusWSClient.EVENT_IO_ERROR, 'client version not fullfill');
            return;
        }
        if (data.code !== this.RES_OK) {
            // this.emit(PinusWSClient.EVENT_IO_ERROR, 'handshake fail');
            return;
        }
        this.handshakeInit(data);
        let obj = this._package.encode(Package.TYPE_HANDSHAKE_ACK);
        this.send(obj);
        if (this.initCallback) {
            this.initCallback(data);
            this.initCallback = null;
        }
    }
    handshakeInit(data) {
        if (data.sys) {
            Routedic.init(data.sys.dict);
            Protobuf.init(data.sys.protos);
        }
        if (data.sys && data.sys.heartbeat) {
            this.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval
            this.heartbeatTimeout = this.heartbeatInterval * 2; // max heartbeat timeout
        }
        else {
            this.heartbeatInterval = 0;
            this.heartbeatTimeout = 0;
        }
        if (typeof this.handshakeCallback === 'function') {
            this.handshakeCallback(data.user);
        }
    }
    send(byte) {
        if (this.socket) {
            this.socket.send(byte.buffer);
        }
    }
    //  private deCompose(msg){
    //    return JSON.parse(Protocol.strdecode(msg.body));
    // }
    emit(event, ...args) {
        let params = [].slice.call(arguments, 1);
        let callbacks = this._callbacks[event];
        if (callbacks) {
            callbacks = callbacks.slice(0);
            for (let i = 0, len = callbacks.length; i < len; ++i) {
                callbacks[i].apply(this, params);
            }
        }
        return this;
    }
}
exports.PinusWSClient = PinusWSClient;
PinusWSClient.DEBUG = false;
class Package {
    encode(type, body) {
        let length = body ? body.length : 0;
        let buffer = new egret.ByteArray();
        buffer.writeByte(type & 0xff);
        buffer.writeByte((length >> 16) & 0xff);
        buffer.writeByte((length >> 8) & 0xff);
        buffer.writeByte(length & 0xff);
        if (body)
            buffer.writeBytes(body, 0, body.length);
        return buffer;
    }
    decode(buffer) {
        let type = buffer.readUnsignedByte();
        let len = (buffer.readUnsignedByte() << 16 | buffer.readUnsignedByte() << 8 | buffer.readUnsignedByte()) >>> 0;
        let body;
        if (buffer.bytesAvailable >= len) {
            body = new egret.ByteArray();
            if (len)
                buffer.readBytes(body, 0, len);
        }
        else {
            console.log('[Package] no enough length for current type:', type);
        }
        return { type: type, body: body, length: len };
    }
}
Package.TYPE_HANDSHAKE = 1;
Package.TYPE_HANDSHAKE_ACK = 2;
Package.TYPE_HEARTBEAT = 3;
Package.TYPE_DATA = 4;
Package.TYPE_KICK = 5;
class Message {
    constructor(routeMap) {
        this.routeMap = routeMap;
    }
    encode(id, route, msg) {
        let buffer = new egret.ByteArray();
        let type = id ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY;
        let byte = Protobuf.encode(route, msg) || Protocol.strencode(JSON.stringify(msg));
        let rot = Routedic.getID(route) || route;
        buffer.writeByte((type << 1) | ((typeof (rot) === 'string') ? 0 : 1));
        if (id) {
            // 7.x
            do {
                let tmp = id % 128;
                let next = Math.floor(id / 128);
                if (next !== 0) {
                    tmp = tmp + 128;
                }
                buffer.writeByte(tmp);
                id = next;
            } while (id !== 0);
            // 5.x
            //                var len:Array = [];
            //                len.push(id & 0x7f);
            //                id >>= 7;
            //                while(id > 0)
            //                {
            //                    len.push(id & 0x7f | 0x80);
            //                    id >>= 7;
            //                }
            //
            //                for (var i:int = len.length - 1; i >= 0; i--)
            //                {
            //                    buffer.writeByte(len[i]);
            //                }
        }
        if (rot) {
            if (typeof rot === 'string') {
                buffer.writeByte(rot.length & 0xff);
                buffer.writeUTFBytes(rot);
            }
            else {
                buffer.writeByte((rot >> 8) & 0xff);
                buffer.writeByte(rot & 0xff);
            }
        }
        if (byte) {
            buffer.writeBytes(byte);
        }
        return buffer;
    }
    decode(buffer) {
        // parse flag
        let flag = buffer.readUnsignedByte();
        let compressRoute = flag & Message.MSG_COMPRESS_ROUTE_MASK;
        let type = (flag >> 1) & Message.MSG_TYPE_MASK;
        let route;
        // parse id
        let id = 0;
        if (type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE) {
            // 7.x
            let i = 0;
            let m;
            do {
                m = buffer.readUnsignedByte();
                id = id + ((m & 0x7f) * Math.pow(2, (7 * i)));
                i++;
            } while (m >= 128);
            // 5.x
            //                var byte:int = buffer.readUnsignedByte();
            //                id = byte & 0x7f;
            //                while(byte & 0x80)
            //                {
            //                    id <<= 7;
            //                    byte = buffer.readUnsignedByte();
            //                    id |= byte & 0x7f;
            //                }
        }
        // parse route
        if (type === Message.TYPE_REQUEST || type === Message.TYPE_NOTIFY || type === Message.TYPE_PUSH) {
            if (compressRoute) {
                route = buffer.readUnsignedShort();
            }
            else {
                let routeLen = buffer.readUnsignedByte();
                route = routeLen ? buffer.readUTFBytes(routeLen) : '';
            }
        }
        else if (type === Message.TYPE_RESPONSE) {
            route = this.routeMap[id];
        }
        if (!id && !(typeof (route) === 'string')) {
            route = Routedic.getName(route);
        }
        let body = Protobuf.decode(route, buffer) || JSON.parse(Protocol.strdecode(buffer));
        return { id: id, type: type, route: route, body: body };
    }
}
Message.MSG_FLAG_BYTES = 1;
Message.MSG_ROUTE_CODE_BYTES = 2;
Message.MSG_ID_MAX_BYTES = 5;
Message.MSG_ROUTE_LEN_BYTES = 1;
Message.MSG_ROUTE_CODE_MAX = 0xffff;
Message.MSG_COMPRESS_ROUTE_MASK = 0x1;
Message.MSG_TYPE_MASK = 0x7;
Message.TYPE_REQUEST = 0;
Message.TYPE_NOTIFY = 1;
Message.TYPE_RESPONSE = 2;
Message.TYPE_PUSH = 3;
class Protocol {
    static strencode(str) {
        let buffer = new egret.ByteArray();
        buffer.length = str.length;
        buffer.writeUTFBytes(str);
        return buffer;
    }
    static strdecode(byte) {
        return byte.readUTFBytes(byte.bytesAvailable);
    }
}
class Protobuf {
    static init(protos) {
        this._clients = protos && protos.client || {};
        this._servers = protos && protos.server || {};
    }
    static encode(route, msg) {
        let protos = this._clients[route];
        if (!protos)
            return null;
        return this.encodeProtos(protos, msg);
    }
    static decode(route, buffer) {
        let protos = this._servers[route];
        if (!protos)
            return null;
        return this.decodeProtos(protos, buffer);
    }
    static encodeProtos(protos, msg) {
        let buffer = new egret.ByteArray();
        for (let name in msg) {
            if (protos[name]) {
                let proto = protos[name];
                switch (proto.option) {
                    case 'optional':
                    case 'required':
                        buffer.writeBytes(this.encodeTag(proto.type, proto.tag));
                        this.encodeProp(msg[name], proto.type, protos, buffer);
                        break;
                    case 'repeated':
                        if (!!msg[name] && msg[name].length > 0) {
                            this.encodeArray(msg[name], proto, protos, buffer);
                        }
                        break;
                }
            }
        }
        return buffer;
    }
    static decodeProtos(protos, buffer) {
        let msg = {};
        while (buffer.bytesAvailable) {
            let head = this.getHead(buffer);
            let name = protos.__tags[head.tag];
            switch (protos[name].option) {
                case 'optional':
                case 'required':
                    msg[name] = this.decodeProp(protos[name].type, protos, buffer);
                    break;
                case 'repeated':
                    if (!msg[name]) {
                        msg[name] = [];
                    }
                    this.decodeArray(msg[name], protos[name].type, protos, buffer);
                    break;
            }
        }
        return msg;
    }
    static encodeTag(type, tag) {
        let value = this.TYPES[type] !== undefined ? this.TYPES[type] : 2;
        return this.encodeUInt32((tag << 3) | value);
    }
    static getHead(buffer) {
        let tag = this.decodeUInt32(buffer);
        return { type: tag & 0x7, tag: tag >> 3 };
    }
    static encodeProp(value, type, protos, buffer) {
        switch (type) {
            case 'uInt32':
                buffer.writeBytes(this.encodeUInt32(value));
                break;
            case 'int32':
            case 'sInt32':
                buffer.writeBytes(this.encodeSInt32(value));
                break;
            case 'float':
                // Float32Array
                let floats = new egret.ByteArray();
                floats.endian = egret.Endian.LITTLE_ENDIAN;
                floats.writeFloat(value);
                buffer.writeBytes(floats);
                break;
            case 'double':
                let doubles = new egret.ByteArray();
                doubles.endian = egret.Endian.LITTLE_ENDIAN;
                doubles.writeDouble(value);
                buffer.writeBytes(doubles);
                break;
            case 'string':
                buffer.writeBytes(this.encodeUInt32(value.length));
                buffer.writeUTFBytes(value);
                break;
            default:
                let proto = protos.__messages[type] || this._clients['message ' + type];
                if (!!proto) {
                    let buf = this.encodeProtos(proto, value);
                    buffer.writeBytes(this.encodeUInt32(buf.length));
                    buffer.writeBytes(buf);
                }
                break;
        }
    }
    static decodeProp(type, protos, buffer) {
        switch (type) {
            case 'uInt32':
                return this.decodeUInt32(buffer);
            case 'int32':
            case 'sInt32':
                return this.decodeSInt32(buffer);
            case 'float':
                let floats = new egret.ByteArray();
                buffer.readBytes(floats, 0, 4);
                floats.endian = egret.Endian.LITTLE_ENDIAN;
                let float = buffer.readFloat();
                return floats.readFloat();
            case 'double':
                let doubles = new egret.ByteArray();
                buffer.readBytes(doubles, 0, 8);
                doubles.endian = egret.Endian.LITTLE_ENDIAN;
                return doubles.readDouble();
            case 'string':
                let length = this.decodeUInt32(buffer);
                return buffer.readUTFBytes(length);
            default:
                let proto = protos && (protos.__messages[type] || this._servers['message ' + type]);
                if (proto) {
                    let len = this.decodeUInt32(buffer);
                    let buf;
                    if (len) {
                        buf = new egret.ByteArray();
                        buffer.readBytes(buf, 0, len);
                    }
                    return len ? Protobuf.decodeProtos(proto, buf) : false;
                }
                break;
        }
    }
    static isSimpleType(type) {
        return (type === 'uInt32' ||
            type === 'sInt32' ||
            type === 'int32' ||
            type === 'uInt64' ||
            type === 'sInt64' ||
            type === 'float' ||
            type === 'double');
    }
    static encodeArray(array, proto, protos, buffer) {
        let isSimpleType = this.isSimpleType;
        if (isSimpleType(proto.type)) {
            buffer.writeBytes(this.encodeTag(proto.type, proto.tag));
            buffer.writeBytes(this.encodeUInt32(array.length));
            let encodeProp = this.encodeProp;
            for (let i = 0; i < array.length; i++) {
                encodeProp(array[i], proto.type, protos, buffer);
            }
        }
        else {
            let encodeTag = this.encodeTag;
            for (let j = 0; j < array.length; j++) {
                buffer.writeBytes(encodeTag(proto.type, proto.tag));
                this.encodeProp(array[j], proto.type, protos, buffer);
            }
        }
    }
    static decodeArray(array, type, protos, buffer) {
        let isSimpleType = this.isSimpleType;
        let decodeProp = this.decodeProp;
        if (isSimpleType(type)) {
            let length = this.decodeUInt32(buffer);
            for (let i = 0; i < length; i++) {
                array.push(this.decodeProp(type, protos, buffer));
            }
        }
        else {
            array.push(this.decodeProp(type, protos, buffer));
        }
    }
    static encodeUInt32(n) {
        let result = new egret.ByteArray();
        do {
            let tmp = n % 128;
            let next = Math.floor(n / 128);
            if (next !== 0) {
                tmp = tmp + 128;
            }
            result.writeByte(tmp);
            n = next;
        } while (n !== 0);
        return result;
    }
    static decodeUInt32(buffer) {
        let n = 0;
        for (let i = 0; i < buffer.length; i++) {
            let m = buffer.readUnsignedByte();
            n = n + ((m & 0x7f) * Math.pow(2, (7 * i)));
            if (m < 128) {
                return n;
            }
        }
        return n;
    }
    static encodeSInt32(n) {
        n = n < 0 ? (Math.abs(n) * 2 - 1) : n * 2;
        return this.encodeUInt32(n);
    }
    static decodeSInt32(buffer) {
        let n = this.decodeUInt32(buffer);
        let flag = ((n % 2) === 1) ? -1 : 1;
        n = ((n % 2 + n) / 2) * flag;
        return n;
    }
}
Protobuf.TYPES = {
    uInt32: 0,
    sInt32: 0,
    int32: 0,
    double: 1,
    string: 2,
    message: 2,
    float: 5
};
Protobuf._clients = {};
Protobuf._servers = {};
class Routedic {
    static init(dict) {
        this._names = dict || {};
        let _names = this._names;
        let _ids = this._ids;
        for (let name in _names) {
            _ids[_names[name]] = name;
        }
    }
    static getID(name) {
        return this._names[name];
    }
    static getName(id) {
        return this._ids[id];
    }
}
Routedic._ids = {};
Routedic._names = {};
//# sourceMappingURL=data:application/json;base64,[encoded data, 32228 characters]