
 "use strict";

 var console    = require('gifer-console');
 var MCServ     = require('net');
 var Service    = require('./service.js');
 var util       = require('util');
 var CMD        = require('./cmd/cmd').CMD;
 var ClientToServCMD = require('./cmdProcessing/clientCMD').ClientToServCMD;
 var _serverCMD      = require('./cmdProcessing/serverCMD');

 var CLIENT_TO_SERVER = 'client-to-server-cmd';
 var SERVER_TO_CLIENT = 'server-to-client-cmd';
 var ServToClientCMD  = _serverCMD.ServToClientCMD;
 // var ClientVersion    = MCServer.ClientVersion;

 var ConnectionsList = {};
 var CRLF            = "\r\n";
 var CR              = "\r";
 var LF              = "\n";
 var wcc             = null;
 var socketIO        = null;
 var errorBuffer     = {};

 Service.LoadJSONFile(MCPathes.Service + 'textsource/errors/client/' + MCServer.Lang + '.errors.json', function(data){
     var text = data;

     if (text){
         global.MCServer.ErrorText = JSON.parse(text.toString());
     }
 }, 'utf8');

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

 function TInBufData(sid, wsEmit){
     var Buffers = [];
     var sID     = sid;
     var Self    = this;

     this.AddDataToInBuf = function(_data){
         if (wsEmit){
             wsEmit.emit(SERVER_TO_CLIENT, CMD.Ping + CR + Service.ObjToString(_data) + CR);
         } else {
             Buffers.push(_data);
         }

         console.info('Added data to InBuffer, total length:' + Buffers.length, console.logLevel.DEBUG);
     };

     this.ClearInBufData = function(){
         if (Buffers.length > 0) {
             Buffers = [];

             console.info('Clearing InBuffer for SessionID: ' + sID, console.logLevel.DEBUG);
         }
     };

     /**
      * @return {string}
      */
     this.GetAllFromInBuf = function(needClear){
         var res = Service.ArrayToString(Buffers);

         if (needClear) {
             Self.ClearInBufData();
         }

         return res;
     };

     /**
      * @return {string}
      */
     this.GetAllFromInBufAsArray = function(needClear){
         var res = Buffers;

         if (needClear) {
             Self.ClearInBufData();
         }

         return res;
     };

     /**
      * @return {string}
      */
     this.GetFromInBuf = function(maxSize){
         var res = "";
         var item = "";

         if (Buffers.length === 1){
             res = Service.ObjToString(Buffers[0]) + CR;

             Buffers.shift();
         } else
         while (Buffers.length > 0){
             item = Service.ObjToString(Buffers[0]);

             res = res + item + CR;

             Buffers.shift();

             if (res.length >= maxSize){
                 break;
             }
         }

         return res;
     };

     this.ClearHistory = function(){
         var i = 0;

         while (i < Buffers.length){
             if (Buffers[i] && Buffers[i].logs) {
                 Buffers.shift();
             } else
             if (Buffers[i]){
                 i ++;
             }
         }
     };
 }

 function ClientConnect (OptSettings) {
     var Self          = this;
     var __socket      = null;
     var __buffer      = "";
     var Send_CallBack = undefined;

     this.socketIO     = OptSettings.io;

     delete OptSettings.io;

     if (console.GetLogLevel() >= console.logLevel.TRACE){
         var pp = OptSettings.pass;
         OptSettings.pass = "*****";
         
         console.log('User try auth and send:\n' + JSON.stringify(OptSettings), console.logLevel.TRACE);

         OptSettings.pass = pp;
     }

     var ConnectOpt = Service.extend({
         login                : 0,
         pass                 : "",
         host                 : '192.168.10.100',
         port                 : 2004,

         sessionID            : "11111111111111",
         authType             : CMD.Login,
         ServPass             : "",

         ClientType           : "web",

         PingDelay            : 25,
         CurrentPing          : 1,

         clientIP             : "",

         HWID                 : "",
         Gender               : 1,                      // пол пользователя, 0 - неизвестен, 1 - мужской, 2 - женский
         Firstname            : "",                     // имя пользователя
         Lastname             : "",                     // фамилия пользователя
         Surname              : "",                     // отчество пользователя
         Avatar               : 0,                      // номер аватара
         Email                : "bugs@nsoft-s.com",     // адрес электронной почты
         SecretQuestionNumber : 0,                      // номер секретного вопроса для восстановления пароля
         SecretAnswer         : "",                     // ответ на секретный вопрос
         State                : 0                       // текущий статус пользователя (0 - свободен)
     }, OptSettings || {});

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

     function ProcessingReceivedData(){
         if (__buffer.indexOf(CRLF) !== -1) {
             var data = Service.GetSubstring(__buffer, CRLF);

             __buffer = Service.GetSubstringFrom(__buffer, CRLF);

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

                 console.log("Received CMD from server:" + CMD_numb, console.logLevel.TRACE);

                 if (DataObj.length > 0) {
                     console.log("Received DATA from server:" + DataObj, console.logLevel.TRACE);

                     DataObj = Service.StringToObj(DataObj);
                 }

                 if (ServToClientCMD[CMD_numb]) {
                     ServToClientCMD[CMD_numb].apply(Self, [DataObj]);
                 } else
                 if (Self.ServerToClientMultiCMD.apply(Self, [CMD_numb, DataObj]) == false){
                     console.err('Server send unknown command: ' + CMD_numb, console.logLevel.ERROR);
                 }

                 if (__buffer.length > 0){
                     process.nextTick(function(){
                         ProcessingReceivedData();
                     });
                 }
             } else {
                 console.warn("Processing Received DATA is empty");
             }
         }
     }

     function enableSSL(socket) {
         var fs = require('fs');

         process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
         process.env.NODE_TLS_ACCEPT_UNTRUSTED_CERTIFICATES_THIS_IS_INSECURE = "1";

         var tlsMCServ = require('tls');
         var constants = require('constants');
         var allowed_ciphers = 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' +
             'DH+AES:RSA+AES:!aNULL:!MD5:!DSS';

         socket.write('mc' + MCServer.ClientVersion + CRLF + '{"Secured":"TLSv1.2"}' + CRLF);

         return tlsMCServ.connect({
             socket            : socket,
             secureProtocol    : "TLSv1_2_method",
             ciphers           : allowed_ciphers,
             honorCipherOrder  : true,
             secureOptions     : constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,
             rejectUnauthorized: false
         });
     }

     function setHandlers(socket) {
         socket.setTimeout(180000);

         socket.on('data', function(data) {
             __buffer += data.toString();

             ProcessingReceivedData();
         });
         socket.on('end', function() {
             console.important('\"End\" connections to MyChat server');

             var err = Self.UsersInBuffer.GetFromInBuf();

             if (err != ""){
                 //console.log('err on end:' + Object.prototype.toString.call( err ));

                 try{
                     err = Service.isString(err) ? JSON.parse(err) : err;
                 } catch (e){
                     //console.err(e);
                 }

                 errorBuffer[ConnectOpt.sessionID] = err;
             }
         });
         socket.on('close', function() {
             console.important('Client disconnected. Close sID: ' + ConnectOpt.sessionID);

             //DropConnection(ConnectOpt.sessionID);
             Self.CloseConnection();
         });
         socket.on('error', function(err) {
             console.err('Error occurred!\n' + JSON.stringify(err), console.logLevel.ERROR);

             Self.CloseConnection(true, err);
         });
         socket.on('timeout', function() {
             console.warn('Timeout occurred! sID: ' + ConnectOpt.sessionID);

             Self.CloseConnection(true, "Timeout occurred!");
         });
     }

     function doConnect() {
         var socket = MCServ.connect({
             port: ConnectOpt.port,
             host: ConnectOpt.host
         }, function() {
             console.log('Connected to MyChat Server. Sending signature');

             try {
                 setHandlers(socket);

                 socket.write('mc' + MCServer.ClientVersion + CRLF + '{"Secured":""}');
             } catch (e) {}
         });

         return socket;
     }

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

     __socket = doConnect();

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

     this.UsersInBuffer = new TInBufData(ConnectOpt.sessionID, ConnectOpt.isWs ? this.socketIO : null);

     this.UserInfo = ConnectOpt;

     this.RightSet = null;

     this.SendDataToServer = function (_data, CallBack) {
         if (_data && (_data !== "")){
             console.log('Sending Data To Server: ' + _data, console.logLevel.TRACE);

             __socket.write(Service.convertToEntities(_data));

             Send_CallBack = CallBack;
         } else {
             console.warn('SendDataToServer. Nothing send to server, _data is empty', console.logLevel.DEBUG);
         }
     };
     
     this.CloseConnection = function(drop, err){
         if (Self.onDisconnect){
             Self.onDisconnect.call(Self, err);
         }

         Self.UsersInBuffer.ClearInBufData();

         if (__socket){
             if (drop){
                 __socket.destroy();
             } else {
                 __socket.end();
             }
         }

         console.log('Close socket with SessionID: ' + ConnectOpt.sessionID, console.logLevel.DEBUG);

         DropConnection(ConnectOpt.sessionID);
     };

     this.checkRights = function(rule){
         return this.RightSet[rule] && this.RightSet[rule] === '1';
     };

     /**
      * @return {boolean}
      */
     this.ServerToClientMultiCMD = function(cmd, _data) {
         var res = false;

         if (Service.InObject(cmd, CMD)) {
             var out  = {};
             out[cmd] = _data || {};

             console.log('ServerToClientMultiCMD. Received cmd: ' + cmd, console.logLevel.TRACE);

             Self.UsersInBuffer.AddDataToInBuf(out);

             res = true;
         }

         return res;
     };

     this.onDisconnect = null;
 }

 function MakeClientConnection (options){
     ConnectionsList[options.sessionID] = new ClientConnect(options);

     return ConnectionsList[options.sessionID];
 }

 /**
 * @return {boolean}
 */
 function DropConnection (sID){
     if (ConnectionsList && ConnectionsList[sID]) {
         delete ConnectionsList[sID];

         console.log('Dropped connection from list, sID: ' + sID, console.logLevel.DEBUG);
     }
 }

 function KILL_ALL_CONNECTION(callback){
     if (ConnectionsList){
         var cc = ConnectionsList;

         ConnectionsList = null;

         for (var i in cc){
             cc[i].CloseConnection(true);
         }

         console.important('All connections was dropped!');

         if (callback) callback();
     }
 }

 /**
 * @return {boolean}
 */
 function HasConnection (id){
     return ConnectionsList && ConnectionsList.hasOwnProperty(id);
 }

 function GetConnection(id){
     return HasConnection(id) ? ConnectionsList[id] : null;
 }

 function GetConnectionInfo(id){
     var info = GetConnection(id);

     return (info) ? info.UserInfo : null;
 }

 function SendDataFromClient (id, CMD_numb, Data){
     if (HasConnection(id)){
         if (ClientToServCMD[CMD_numb]){
             // if ((CMD_numb === CMD.cs_special_ping) && (ConnectionsList[id].UserInfo.CurrentPing < ConnectionsList[id].UserInfo.PingDelay)){
             //     ConnectionsList[id].UserInfo.CurrentPing ++;
             //
             //     console.log('Skip cs_ping', console.logLevel.L_Full);
                 // console.log('Skip cs_ping');
             // } else {
                 if (Service.isString(Data)){
                    Data = Data.split(CR);
                 }

                 if (CMD_numb === CMD.cs_special_ping) {
                     ConnectionsList[id].UserInfo.CurrentPing = 1;
                 } else {
                     console.log('SendDataFromClient CMD:' + CMD_numb, console.logLevel.DEBUG);
                 }

                 if (Data && Data.length > 0) {
                     Data.forEach(function(item, idx, arr){
                         arr[idx] = item == 'true' ? true : (item == 'false' ? false : item);
                     });
                 } else {
                     console.warn('CMD:' + CMD_numb + ' Client data is EMPTY!', console.logLevel.DEBUG);
                 }

                 var _out = ClientToServCMD[CMD_numb].apply(GetConnection(id), Data);

                 if (CMD_numb !== CMD.cs_special_ping) {
                     console.log('SendDataFromClient CMD:' + CMD_numb + ' Data: ' + Data, console.logLevel.DEBUG);
                 } else {
                     console.log('SendDataFromClient CMD:' + CMD_numb + ' Data: ' + Data, console.logLevel.DEBUG);
                     // console.log('SendDataFromClient PING!'); // , console.logLevel.L_Extended
                 }

                 ConnectionsList[id].SendDataToServer(_out);
             // }
         } else {
             console.err('CMD numb at SendDataFromClient: ' + CMD_numb + ' is not defined! Add to ClientToServCMD list!', console.logLevel.ERROR);
         }
     }
 }

 function startSocketIO(IO){
     socketIO = IO;

     socketIO.on('connection', function(client){
         console.info('Someone connected. IP \"' + client.conn.remoteAddress + '\"', console.logLevel.DEBUG);
         console.log(client.handshake.query, console.logLevel.DEBUG);

         client.sendDataInternal = require('./mctools/mcInternalConnection').sendDataFromInternalServer;

         function send(data) {
             client.emit(SERVER_TO_CLIENT, data);
         }

         client.on(CLIENT_TO_SERVER, function(data){
             var reqData = Service.Get_CMD(data);

             wcc.UniversalParser.apply(client, [reqData.CMD, reqData.DATA, send, true]);
         });

         client.on('disconnect', function(){
             console.info('Someone disconnected. IP \"' + client.conn.remoteAddress + '\"', console.logLevel.DEBUG);

             if (HasConnection(client.mcSessionID)){
                 GetConnection(client.mcSessionID).CloseConnection();
             }
         });
     });
 }

 function checkUploadRights(id) {
     var res = false;

     if (ConnectionsList && ConnectionsList[id]) {
         res = ConnectionsList[id].checkRights(CMD.RS.QSendImages) ||
               ConnectionsList[id].checkRights(CMD.RS.QImagesPrivates);
     }

     return res;
 }

 function checkRightsByIds(id, rights) {
     var res = false;

     if (ConnectionsList && ConnectionsList[id] && rights.length) {
         rights.forEach(function(item){
             res = res || ConnectionsList[id].checkRights(item);
         });
     }

     return res;
 }

 function checkRightsByIdsStrict(id, rights) {
     var res = false;

     if (ConnectionsList && ConnectionsList[id] && rights.length) {
         rights.forEach(function(item){
             res = res && ConnectionsList[id].checkRights(item);
         });
     }

     return res;
 }

 function getFromErrorBuffer(sID){
     var res = {};

     if (errorBuffer && errorBuffer.hasOwnProperty(sID)){
         res = errorBuffer[sID];

         delete errorBuffer[sID];
     }

     return res;
 }

 function GetFirstSID(){
     return ConnectionsList ? Object.keys(ConnectionsList)[0] : null;
 }

 function isAdminSid(sid, uin) {
     var user = GetConnectionInfo(sid);

     return user && user.ClientType === CMD.ClientType.WEB_ADMIN && user.UIN == uin;
 }

 // =======================================================
 // =======================================================
 
 exports.MakeClientConnection = MakeClientConnection; 
 exports.HasConnection        = HasConnection;
 exports.GetConnection        = GetConnection;
 exports.SendDataToServer     = SendDataFromClient;
 exports.isAdminSid           = isAdminSid;
 exports.CMD                  = CMD;
 exports.KILL_ALL_CONNECTION  = KILL_ALL_CONNECTION;
 exports.getPenaltyUser       = _serverCMD.getPenaltyUser;
 exports.startSocketIO        = startSocketIO;

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

 exports.firstLogin           = _serverCMD.firstLogin;
 exports.httpStarted          = function () {
     wcc = _serverCMD.httpStarted();
 };
 exports.checkUploadRights    = checkUploadRights;
 exports.checkRightsByIds     = checkRightsByIds;
 exports.checkRightsByIdsStrict = checkRightsByIdsStrict;
 exports.getFromErrorBuffer   = getFromErrorBuffer;
 exports.GetFirstSID          = GetFirstSID;
