"use strict";

const console = require('gifer-console');
const CMD     = require('./cmd/cmd').CMD;
const Service = require('./service');
const http    = require('http');

const ANDROID_TO_SERVER = 'A_T_S';
const SERVER_TO_ANDROID = 'S_T_A';
const SET_TOKEN         = 'S_TK';
const SET_CONFIG        = 'S_C';
// const NEED_RECONNECT    = 'NRC';
const SOCKET_READY      = 'SC_RD';
const LIVE_RECONNECT    = 'LV_RC';
const SERVER_RESTART    = 'RECON';
// const SPECIAL_PING      = 'PING';
const PUSH_PASSWORD     = 'PWD';
const DEBUG_PUSH        = 'D_P';
const ANDROID_PUSH      = 'A_P'; // андроид сигнализирует, что его снимают из памяти, нужно отправлять пуши
const MCTEST            = 'MCTEST';
const SET_STATE         = 'STATE';
const MTERROR           = 'MTERROR';
const WS_ERROR          = 'ERR';
const pingInterval      = 1000 * 30;
const MagicPacket       = '\u0017\u0006';
const SendingFlags      = '00';
const CRLF              = '\r\n';
const oldToken          = 'reconnected webSocket';
const __PUSH_PWD__      = Service.RandomHash(30);
// const pushServerIP      = "127.0.0.1";
const pushServerIP      = "mychat-server.com";
const pushServerPort    = "52020";
const versionPush       = "1.0";
const pushMessagesList  = [
    CMD.sc_halt,
    CMD.sc_private,
    CMD.sc_media_call,
    CMD.sc_error,
    CMD.sc_broadcast,
    CMD.sc_media_incoming_call_accepted_by_another_cid,
    CMD.sc_private_read,
    CMD.sc_private_read_notify,
    CMD.sc_conf_read,
    CMD.sc_get_geo_position,
    CMD.sc_remove_history,
    CMD.sc_remove_my_acc
];
const errCodes          = {
    noToken : {
        code : 1,
        error: "Token is empty"
    }
}

var multiSend   = {};
var startedPush = {};

const internalSockets     = new function () {
    var list = {};
    var self = this;

    this.has = function (token) {
        return list.hasOwnProperty(token);
    };

    this.add = function (client, token, /*onChangeToken,*/ info) {
        list[token] = new CreateInternalSocket(client, /*token,*/ info/*, function changeToken (newToken) {
            list[newToken] = list[token]; // изменение старого токена, он пришоел без уина, так как логин был через
                                          // почту или ник, уин приходит после логина и его нужно было вписать в токен

            onChangeToken && onChangeToken(newToken);

            delete list[token];
        }*/);

        list[token].onClose = function (closeToken) {
            if (list[closeToken]){
                delete list[closeToken];
            }
        };
    };

    this.startPing = function (token) {
        self.has(token) && list[token].startPing(list[token].State);
    };

    this.stopPing = function (token) {
        self.has(token) && list[token].stopPing(token);
    };

    this.setPWD = function (token, pwd) {
        self.has(token) && list[token].setPWD(pwd);
    };

    this.setSate = function (newState, token) {
       if (self.has(token)) {
           list[token].State = newState;
       }
    };

    this.setDebugPushServer = function (token){
        if (self.has(token)) {
            list[token].DebugPush = true;
        }
    }

    this.canSend = function (token) {
        return self.has(token) && list[token].canSend();
    };

    this.setNewWsClient = function (token, webSocketClient) {
        self.has(token) && list[token].setNewWsClient(webSocketClient);
    };

    this.disconnect = function (token) {
        self.has(token) && list[token].disconnect();
    }
};

// ===============================================================

function _sendPushMessage(data){
    if (data.length){
        const options = {
            hostname: pushServerIP,
            port    : pushServerPort,
            path    : "/",
            method  : 'POST',
            timeout : 200,
            headers : {
                'Content-Type'  : 'application/json',
                'Connection'    : 'keep-alive',
                'Content-Length': Buffer.byteLength(data),
                'PushProtocol'  : versionPush,
                'MyChatVersion' : MCServer.ClientVersion
            }
        };

        console.log("data length: " + data.length + " :: " + Buffer.byteLength(data), console.logLevel.DEBUG);

        const req = http.request(options, res => {
            console.log('statusCode: ' + res.statusCode, console.logLevel.DEBUG);

            res.on('data', resData => {
                if (resData.indexOf("Ok") === 0) {} else {
                    var inv = [];

                    try {
                        inv = JSON.parse(resData);
                    } catch (e){}

                    if (Service.isArray(inv)){
                        console.log("invalid tokens [" + inv.length + "]:\n" + inv);

                        try {
                            inv = inv.join(',')
                        } catch (e) {
                            inv += "";
                        }
                    } else
                    if (Service.isObject){
                        inv = resData;

                        console.log("invalid tokens: " + resData);
                    }

                    require('./mctools/mcInternalConnection').sendDataFromInternalServer(CMD.ns_remove_tokens + "=" + inv);
                }
            });
        });

        req.on('error', error => {
            console.log("_sendPushMessage: " + error);
        });

        req.write(data);
        req.end();
    } else {
        console.err("Try to send empty data: " + data, console.logLevel.ERROR);
    }
}

// ===============================================================

function CreateInternalSocket (_wsClient, /*initToken,*/ info /*_changeToken*/) {
    var MCServ    = require('net');
    var wsClient  = null;
    var Self      = this;
    var aSocket   = null;
    var pingTimer = null;
    var __buffer  = "";
    var hasIP     = false;
    var pingCount = 0;
    var userInfo  = {
        token    : "", // EEBFF163-7DB7-476C-BD45-445876A60B2D + CRLF
        androidID: "", // 84f9567feba19ad34ba068e1facd29759459ed221c666de91f6a73b40bdd4785 + CRLF
        serverID : "", // 9F3D5AA + CRLF
        UIN      : -1, // 16222 + CRLF
        type     : "", // apple
        getToken : function (cmd, spec){
            var res = userInfo.token + CRLF + userInfo.androidID + CRLF + userInfo.serverID + CRLF + userInfo.UIN + CRLF + userInfo.type;

            if (userInfo.config){
                switch (cmd){
                    case CMD.sc_media_incoming_call_accepted_by_another_cid:
                    case CMD.sc_media_call:
                        res = userInfo.token + CRLF + userInfo.config.voipToken + CRLF + userInfo.serverID + CRLF + userInfo.UIN + CRLF + userInfo.type;
                    break;

                    case CMD.sc_private:
                        if (spec === "voip"){
                            res = userInfo.token + CRLF + userInfo.config.voipToken + CRLF + userInfo.serverID + CRLF + userInfo.UIN + CRLF + userInfo.type;
                        }
                    break;
                }
            }

            return res;
        }
    };

    userInfo = Service.MargeObj(userInfo, info);

    // =====================================================

    function _bbs(multiSend, CMD_numb){
        let id = "BBS";
        // let tParts = token.split(CRLF);

        let androidID   = userInfo.androidID;    //tParts[1];
        let servID      = userInfo.serverID; //tParts[2];
        let serviceType = userInfo.type;     //tParts[4] || "google";

        if (androidID){
            if (!multiSend.hasOwnProperty(id)){
                multiSend[id] = {
                    tokens: {
                        google: [],
                        huawei: [],
                        apple : [],
                        count : 0
                    }
                }

                multiSend[id].tokens[serviceType] = [androidID];
                multiSend[id].tokens.count ++ ;

                setTimeout(function (){
                    _sendPushMessage(JSON.stringify({
                        servID: servID,
                        multi : true,
                        tokens: multiSend[id].tokens,
                        debug : false, //Self.DebugPush,
                        data  : {
                            CMD   : CMD_numb,
                            body  : "",
                            encryptedPush: "false"
                        }
                    }));

                    delete multiSend[id];
                }, 100);
            } else {
                if (multiSend[id].tokens[serviceType]) {
                    multiSend[id].tokens[serviceType].push(androidID);
                } else {
                    console.err("_bbs serviceType: " + serviceType, console.logLevel.ERROR);
                }

                multiSend[id].tokens.count ++ ;
            }
        } else {
            console.log("no token for: " + userInfo.getToken());

            _disconnect();
        }
    }

    function _chMessage(multiSend, DataObj, CMD_numb){
        if (DataObj.length > 0) {
            DataObj = encrypt(CMD_numb, DataObj, __PUSH_PWD__);
        }

        let id = DataObj.plainObj.UID + "_" + DataObj.plainObj.Idx;
        // let tParts = token.split(CRLF);

        let androidID   = userInfo.androidID;//tParts[1];
        let servID      = userInfo.serverID; //tParts[2];
        let serviceType = userInfo.type;     //tParts[4] || "google";

        if (androidID){
            if (!multiSend.hasOwnProperty(id)){
                multiSend[id] = {
                    data  : DataObj.encrypted,
                    enc   : "true",
                    tokens: {
                        google: [],
                        huawei: [],
                        apple : [],
                        count : 0
                    }
                }

                multiSend[id].tokens[serviceType] = [androidID];
                multiSend[id].tokens.count ++ ;

                setTimeout(function (){
                    _sendPushMessage(JSON.stringify({
                        servID: servID,
                        multi : true,
                        tokens: multiSend[id].tokens,
                        debug : false, //Self.DebugPush,
                        data  : {
                            CMD  : CMD_numb,
                            body : multiSend[id].data,
                            encryptedPush: multiSend[id].enc
                        }
                    }));

                    delete multiSend[id];
                }, 100);
            } else {
                if (multiSend[id].tokens[serviceType]) {
                    multiSend[id].tokens[serviceType].push(androidID);
                } else {
                    console.err("_chMessage serviceType: " + serviceType, console.logLevel.ERROR);
                    console.err("_chMessage token: " + userInfo.token, console.logLevel.ERROR);
                }

                multiSend[id].tokens.count ++ ;
            }
        } else {
            console.log("no token for: " + userInfo.getToken());

            _disconnect();
        }
    }

    function _singleMessage(DataObj, CMD_numb, specialOptions){
        if (DataObj.length > 0) {
            DataObj = encrypt(CMD_numb, DataObj, __PUSH_PWD__);
        }

        if (userInfo.token){
            _sendPushMessage(JSON.stringify({
                CMD  : CMD_numb,
                token: userInfo.getToken(CMD_numb, specialOptions),
                encryptedPush: "true",
                debug: Self.DebugPush,
                body : DataObj.encrypted,
                spec : specialOptions
            }));

            if (CMD_numb === CMD.sc_error){
                switch (DataObj.ErrNum) {
                    case 3: case 4: case 14: case 15: case 21: case 23: case 24: case 44:
                    case 60: case 71: case 79: case 80: case 81: case 82: case 92:
                        console.log("Receive MyChat error: #" + DataObj.ErrNum);

                        _stopPing(userInfo.token);
                }
            }

            if (CMD_numb === CMD.sc_halt){
                _disconnect();
            }
        } else {
            console.log("no token for: " + userInfo.getToken());

            _disconnect();
        }
    }

    // =====================================================

    function _stopPing(token){
        console.immediately("STOP ping timer for: " + userInfo.androidID + "\npingTimer:" + pingTimer, console.logLevel.DEBUG);

        if (startedPush[token]) {
            console.log("startedPush remove: " + token + " : " + startedPush[token].androidID);

            delete startedPush[token];
        }

        clearInterval(pingTimer);
        pingTimer = null;
    }

    function _startPing(state) {
        if (pingTimer) {
            return;
        }

        pingCount = 0;

        if (!pingTimer && _canSend() && state !== CMD.userStates.away && state !== CMD.userStates.dnd){
            console.log("Set state sway", console.logLevel.DEBUG);
            
            aSocket.write(MagicPacket + CMD.cs_set_state + SendingFlags + JSON.stringify({
                State: CMD.userStates.away
            }) + CRLF);
        }

        pingTimer = setInterval(function () {
            if (pingTimer && _canSend()){
                if (userInfo.androidID){
                    aSocket.write(MagicPacket + CMD.cs_ping + SendingFlags + CRLF);

                    pingCount ++;

                    if (pingCount === 2880 || pingCount === 5760 || pingCount === 8640){
                        if (userInfo.type !== "apple") // Гера попросив не слати пінги еплу, тимчасово...
                        _sendPushMessage(JSON.stringify({
                            multi : true,
                            servID: userInfo.serverID,
                            debug : Self.DebugPush,
                            tokens: {
                                huawei: [].concat(userInfo.type === "huawei" ? [userInfo.androidID] : []),
                                apple : [].concat(userInfo.type === "apple"  ? [userInfo.androidID] : []),
                                google: [].concat(userInfo.type === "google" ? [userInfo.androidID] : [])
                            },
                            data  : {
                                CMD   : CMD.sc_ping,
                                body  : "SPECIAL_PING",
                                encryptedPush: "false"
                            }
                        }));
                    }

                    if (pingCount > 8640){
                        _disconnect();
                    }
                } else {
                    _disconnect();
                }
            } else {
                _disconnect();
            }
        }, pingInterval);

        startedPush[userInfo.token] = userInfo;

        console.log("startedPush added new: " + userInfo.token + " : " +userInfo.androidID);

        console.log("Start ping timer for: " + userInfo.androidID + "\npingTimer: " + pingTimer, console.logLevel.DEBUG);
    }

    function _disconnect() {
        _stopPing(userInfo.token);

        if (aSocket) {
            aSocket.write(MagicPacket + CMD.cs_quit + SendingFlags + CRLF);
        }

        setTimeout(function () {
            try {
                aSocket.destroy();
            } catch (e) {}
        }, 20);
    }

    function _setNewWsClient(_wsClient) {
        wsClient = _wsClient;

        hasIP = false;

        wsClient.on(ANDROID_TO_SERVER, function (data) {
            if (_canSend()){
                if (!hasIP) {
                    var _data    = Service.GetSubstring(data, CRLF);
                    var CMD_numb = _data.slice(2, 6) + '';
                    var DataObj  = _data.slice(8, _data.length);

                    // console.log("<<< data: " + data);

                    if (CMD_numb === CMD.cs_hello) {
                        console.log('get hello, IP: ' + wsClient.handshake.address, console.logLevel.DEBUG);

                        hasIP   = true;
                        DataObj = Service.StringToObj(DataObj);
                        
                        aSocket.write(MagicPacket + CMD.cs_hello + SendingFlags +
                            JSON.stringify(Service.MargeObj(DataObj, {
                                "NodeRemoteIP": wsClient.handshake.address,
                            })) + CRLF
                        );
                    } else

                    if (CMD_numb === CMD.cs_live_reconnect) {
                        console.log('get live_reconnect, IP: ' + wsClient.handshake.address, console.logLevel.DEBUG);

                        hasIP   = true;
                        DataObj = Service.StringToObj(DataObj);

                        aSocket.write(MagicPacket + CMD.cs_live_reconnect + SendingFlags +
                            JSON.stringify(Service.MargeObj(DataObj, {
                                "NodeRemoteIP": wsClient.handshake.address,
                            })) + CRLF
                        );
                    } else

                    {
                        aSocket.write(data);
                    }
                } else {
                    aSocket.write(data);

                    // console.log("ANDROID_TO_SERVER: " + data);
                }
            }
        });

        wsClient.on(SET_CONFIG, function (_config) {
            userInfo.config = Service.StringToObj(_config);

            console.log("SET_CONFIG: " + _config, console.logLevel.DEBUG);
        });
    }

    function encrypt(CMD_numb, _DataObj, _pushPWD){
        var encData = "";
        var tempObj = Service.StringToObj(_DataObj);

        if ((CMD_numb === CMD.sc_put_msg2txt_channel || CMD_numb === CMD.sc_private) && tempObj.Msg && tempObj.Msg.length > 1024){
            tempObj.Msg = tempObj.Msg.slice(0, 1024) + "...";
        } else
        if (CMD_numb === CMD.sc_broadcast && tempObj.msg && tempObj.msg.length > 1024){
            tempObj.msg = tempObj.msg.slice(0, 1024) + "...";
        }

        if (_pushPWD) {
            encData = Service.EncryptBase64(_pushPWD, JSON.stringify(tempObj));
        }

        return {
            encrypted : encData,
            plainObj  : tempObj
        }
    }

    function _ProcessingReceivedData(/*token*/){
        if (__buffer.indexOf(CRLF) > 0) {
            var data = Service.GetSubstring(__buffer, CRLF);

            __buffer = Service.GetSubstringFrom(__buffer, CRLF);

            if (data !== ''){
                // var indata = data;
                var CMD_numb = data.slice(2, 6) + '';
                var DataObj  = data.slice(8, data.length);

                //console.log("Received CMD from server:" + CMD_numb);
                console.log(userInfo.token, console.logLevel.DEBUG);
                console.log(data, console.logLevel.DEBUG);

                // === BBS messages ===
                if (CMD_numb === CMD.sc_bbs_changed){
                    _bbs(multiSend, CMD_numb);
                } else
                    
                // === conf messages ===
                if (CMD_numb === CMD.sc_put_msg2txt_channel) {
                    var hash = Service.RandomHash(7);
                    var obj  = Service.StringToObj(DataObj);

                    // если отправлено сообщение от пользователя у которого есть эпле, то на эпле на пушах не нужно возвращать это сообщение 05,05,2022 for Glubber
                    if (obj.UIN == userInfo.UIN && userInfo.type === "apple"){

                    } else

                    // добавляет имя юзера и название конфы для отображения алерта на мобильнике
                    require('./mctools/mcInternalConnection').sendDataFromInternalServer(CMD.ns_get_user_conf_info + "=" + JSON.stringify({
                        hash: hash,
                        uin: obj.UIN,
                        uid: obj.UID
                    }), function (data){
                        // console.immediately("obj: " + (typeof obj) + " " + JSON.stringify(obj));
                        // console.immediately("indata: " + indata);
                        // console.immediately("data: " + JSON.stringify(data));

                        obj.DisplayName = data.DisplayName;
                        obj.ConfName    = data.ConfName;

                        _chMessage(multiSend, JSON.stringify(obj), CMD_numb);
                    }, hash);
                } else

                // === various single messages ===
                if (pushMessagesList.indexOf(CMD_numb) >= 0) {
                    switch (CMD_numb) {
                        case CMD.sc_private:
                            var hash = Service.RandomHash(7);
                            var obj  = Service.StringToObj(DataObj);
                            var spec = null;

                            if (userInfo.type === "apple" && obj.MsgType === 12) { // MEDIA_CALL : 12,
                                spec = "voip";
                            }

                            // если отправлено сообщение от пользователя у которого есть эпле, то на эпле на пушах не нужно возвращать это сообщение 05,05,2022 for Glubber
                            if (obj.UINFrom == userInfo.UIN && userInfo.type === "apple"){

                            } else

                            // добавляет имя юзера для отображения алерта на мобильнике
                            require('./mctools/mcInternalConnection').sendDataFromInternalServer(CMD.ns_get_user_conf_info + "=" + JSON.stringify({
                                hash: hash,
                                uin: obj.UINFrom
                            }), function (data){
                                obj.DisplayName = data.DisplayName;

                                _singleMessage(JSON.stringify(obj), CMD_numb, spec);
                            }, hash);
                        break;

                        case CMD.sc_get_geo_position:
                            if (!DataObj) DataObj = "{}";
                            _singleMessage(DataObj, CMD_numb);
                        break;

                        default:
                            _singleMessage(DataObj, CMD_numb);
                    }
                } else

                // === change password ===
                if (CMD_numb === CMD.sc_change_password){
                    console.log("User password changed - send halt");

                    _sendPushMessage(JSON.stringify({
                        CMD  : CMD.sc_halt, // halt
                        debug: Self.DebugPush,
                        token: userInfo.getToken(),
                        encryptedPush: "false",
                        body : "HALT"
                    }));

                    _disconnect();
                }

                if (__buffer.length > 0){
                    process.nextTick(function(){
                        _ProcessingReceivedData(/*token*/);
                    });
                }
            } else {
                console.warn("Processing Received DATA is empty", console.logLevel.DEBUG);
            }
        }
    }

    function _canSend() {
        var res = false;

        try {
            res = aSocket && !aSocket.destroyed && aSocket.writable
        } catch (e){
            console.log("token: " + userInfo.androidID + "\n" + e.message);
        }

        return res;
    }

    // function _setUinForToken(uin) {
    //     var _token = token.split(CRLF);
    //
    //     _token[3] = uin;
    //     _token    = _token.join(CRLF);
    //     aSocket.androidToken = _token;
        // _changeToken(_token);
        //
        // return _token;
    // }

    function _setPWD(pwd) {  // deprecated
        // pushPWD = pwd;
    }

    // =====================================================

    _setNewWsClient(_wsClient);

    aSocket = MCServ.connect({
        port: MCServer.MCPort,
        host: MCServer.Host
    }, function () {
        console.log('Android client connected to MyChat Server', console.logLevel.DEBUG);
    });

    // aSocket.androidToken = userInfo.token;

    // var noUIN = token.split(CRLF)[3] == "-1";
    var noUIN = userInfo.UIN == -1;

    aSocket.on('data', function (inData) {
        if (wsClient && wsClient.connected) {
            var data     = Service.GetSubstring(inData, CRLF);
            var CMD_numb = data.slice(2, 6) + '';

            if (CMD_numb === CMD.sc_hello) {
                var DataObj  = data.slice(8, data.length);

                userInfo.serverID = Service.StringToObj(DataObj).ID;
            } else
            if (CMD_numb === CMD.sc_login && noUIN){
                var DataObj  = data.slice(8, data.length);

                noUIN = false;

                userInfo.UIN = Service.StringToObj(DataObj).UIN;
                
                // token = _setUinForToken(Service.StringToObj(DataObj).UIN);
            } else
            if (CMD_numb === CMD.sc_halt){
                _disconnect();
            }

            // console.log(">>> outData: " + inData);

            wsClient.emit(SERVER_TO_ANDROID, inData);
        } else {
            __buffer += inData.toString();

            _ProcessingReceivedData(/*aSocket.androidToken*/);
        }
    });

    aSocket.on('error', function (err) {
        console.err('Internal android socket error:\n' + JSON.stringify(err), console.logLevel.ERROR);
    });

    aSocket.on('close', function () {
        console.immediately('Internal android socket closed! ' + userInfo.androidID, console.logLevel.DEBUG);

        if (wsClient) {
            console.log('Local WebSocket disconnect!', console.logLevel.DEBUG);

            wsClient.quit = true;
            wsClient.disconnect();
        }

        if (Self.onClose && aSocket){
            Self.onClose(userInfo.token/*aSocket.androidToken*/);
        }

        aSocket = null;

        _stopPing(userInfo.token);
    });

    this.canSend        = _canSend;
    this.setNewWsClient = _setNewWsClient;
    this.getSocket      = null;
    this.onClose        = null;
    this.stopPing       = _stopPing;
    this.disconnect     = _disconnect;
    this.startPing      = _startPing;
    this.setPWD         = _setPWD;
    this.DebugPush      = false;

    setTimeout(function () {
        wsClient.emit(SOCKET_READY, __PUSH_PWD__);
    }, 100);
}

// ===============================================================

function init (socketIO) {
    var wsConnections = {};

    // console.SetLogLevel(console.logLevel.L_Full);

    socketIO.on('connection', function (webSocketClient) {
        function startPush(_token) {
            if (_token && wsConnections.hasOwnProperty(_token) /*&& internalSockets.has(_token)*/){
                // internalSockets[_token].startPing();
                internalSockets.startPing(_token);

                try {
                    wsConnections[webSocketClient.mcToken].quit = true;
                    wsConnections[webSocketClient.mcToken].disconnect();
                } catch (e){
                    console.log(e.message);
                }

                try {
                    delete wsConnections[webSocketClient.mcToken];
                } catch (e) {}

            }/* else {
                console.err("startPush error, token: " + _token);
            }*/
        }

        console.info('Someone connected. IP \"' + webSocketClient.conn.remoteAddress + '\"', console.logLevel.DEBUG);

        var token = "";

        webSocketClient.on(MCTEST, function (_pwd) {
            var testSocket = Service.testServerConnect(MCServer.Host, MCServer.MCPort, function (err, data) {
                if (err){
                    webSocketClient.emit(MTERROR, err);
                } else {
                    webSocketClient.emit(MCTEST, data);
                }

                try {
                    testSocket.destroy();
                } catch (e) {}

                testSocket = null;
            });
        });

        webSocketClient.on(PUSH_PASSWORD, function (_pwd) {
            if (webSocketClient.mcToken /*&& internalSockets.has(webSocketClient.mcToken)*/) {
                // internalSockets[webSocketClient.mcToken].setPWD(_pwd);
                internalSockets.setPWD(webSocketClient.mcToken, _pwd);
            }
        });

        webSocketClient.on(SET_STATE, function (state) {
            if (webSocketClient.mcToken /*&& internalSockets.has(webSocketClient.mcToken)*/) {
                // internalSockets[webSocketClient.mcToken].setPWD(_pwd);
                internalSockets.setSate(webSocketClient.mcToken, state);
            }
        });

        webSocketClient.on(DEBUG_PUSH, function () {
            if (webSocketClient.mcToken) {
                internalSockets.setDebugPushServer(webSocketClient.mcToken);
            }
        });

        webSocketClient.on(SET_TOKEN, function (_token, cliVer) {
            // token     // websocket // EEBFF163-7DB7-476C-BD45-445876A60B2D + CRLF
            // androidID // token     // 84f9567feba19ad34ba068e1facd29759459ed221c666de91f6a73b40bdd4785 + CRLF
            // serverID  // 9F3D5AA + CRLF
            // UIN       // 16222 + CRLF
            // type      // apple

            let _userInfo = _token.toString().split(CRLF);

            _userInfo = {
                token    : _userInfo[0],
                androidID: _userInfo[1],
                serverID : _userInfo[2],
                UIN      : _userInfo[3],
                type     : _userInfo[4],
                cliVer   : cliVer
            };
            
            token = _userInfo.token;

            if (token){
                // if (wsConnections.hasOwnProperty(token)){
                //     wsConnections[token].mcToken = oldToken;
                // }

                wsConnections[token] = webSocketClient;

                webSocketClient.mcToken  = token;
                webSocketClient.userInfo = _userInfo;

                if (internalSockets.canSend(token)){
                    console.log('WebSocket: reconnect with exist socket ' + token, console.logLevel.DEBUG);

                    internalSockets.setNewWsClient(token, webSocketClient);
                    internalSockets.stopPing(token);

                    webSocketClient.emit(LIVE_RECONNECT, __PUSH_PWD__);
                } else {
                    console.log('WebSocket: new connection ' + token, console.logLevel.DEBUG);

                    internalSockets.add(webSocketClient, token, _userInfo /* function onChangeToken (newToken) {
                        webSocketClient.mcToken = newToken;
                        token = newToken;
                    },*/);
                }
            } else {
                console.log(JSON.stringify(errCodes.noToken) + "\n" + _token);

                webSocketClient.emit(WS_ERROR, JSON.stringify(errCodes.noToken));

                setTimeout(function () {
                    webSocketClient.quit = true;
                    webSocketClient.disconnect();
                }, 100);
            }
        });

        webSocketClient.on(ANDROID_PUSH, function (_token) {
            let _userInfo = _token.toString().split(CRLF);

            _userInfo = {
                token    : _userInfo[0],
                androidID: _userInfo[1],
                serverID : _userInfo[2],
                UIN      : _userInfo[3],
                type     : _userInfo[4]
            };

            if (!webSocketClient.userInfo){
                console.log("ANDROID_PUSH: " + _userInfo);

                if (_userInfo.token && _userInfo.token.length > 10){
                    token = _userInfo.token;

                    webSocketClient.userInfo = _userInfo;
                }
            }

            try {
                startPush(webSocketClient.userInfo.token);
            } catch (e){
                console.err(e.message, console.logLevel.ERROR);

                webSocketClient.quit = true;
                webSocketClient.disconnect();
            }
        });

        webSocketClient.on('timout', function () {
            console.err('WebSocket timeout! ' + token, console.logLevel.ERROR);
        });

        webSocketClient.on('error', function (err) {
            console.err('WebSocket error occurred! ' + token + '\n' + JSON.stringify(err), console.logLevel.ERROR);
        });

        webSocketClient.on('disconnect', function () {
            if (webSocketClient.quit) {
                if (wsConnections[token]){
                    try {
                        delete wsConnections[token]
                    } catch (e){}
                }
            } else
            if (token) {
                console.warn('WebSocket disconnected: ' + (webSocketClient.userInfo ? webSocketClient.userInfo.androidID : token), console.logLevel.DEBUG);

                startPush(token);
            } else {
                // not android | iOS client
            }
        });
    });

    if (MCServer.PushTokens.length){
        var tokens = {
            google: [],
            huawei: [],
            apple : [],
            count : 0
        };
        var invalid = [];
        var glubberToken = "";

        MCServer.PushTokens.forEach(function (_token) {
            let arr         = _token.split(CRLF);
            let token       = arr[1];
            // let token       = _token.split(CRLF)[0];
            let serviceType = arr[4] || "google";

            // if (arr[3] == 16222){ // UIN
            //     glubberToken = _token;
            // }

            if (token && token.length > 10){
                tokens[serviceType].push(token);
                tokens.count ++ ;
            } else {
                console.immediately("invalid token: " + _token, console.logLevel.DEBUG);
                invalid.push(_token);
            }
        });

        console.log("Restart android connect by token:\n" + JSON.stringify(tokens));

        if (invalid.length){
            require('./mctools/mcInternalConnection').sendDataFromInternalServer(CMD.ns_remove_tokens + "=" + invalid.join(","));
        }

        _sendPushMessage(JSON.stringify({
            servID: MCServer.ServID,
            multi : true,
            tokens: tokens,
            data  : {
                CMD   : SERVER_RESTART, // recon
                body  : { needReconnect : true },
                encryptedPush: "false"
            }
        }));

        // if (glubberToken){
        //     _sendPushMessage(JSON.stringify({
        //         CMD  : SERVER_RESTART,
        //         token: glubberToken,
        //         encryptedPush: "false",
        //         debug: true,
        //         body : JSON.stringify({ needReconnect : true })
        //     }));
        // }
    } else {
        console.log("MCServer.PushTokens is empty");
    }

    // this.close = function () {
    //     console.immediately("close & quit");
    //
    //     Object.keys(wsConnections).forEach(function (t) {
    //         wsConnections[t].disconnect();
    //     });
    // };
}


module.exports = init;

