class GameSocket
{
    constructor (scene, server, port, protocolId, token, userId, gameId, casinoId, currency, originId, bonusId)
    {
        this.scene = scene;

        this.server = server;
        this.port = port;
        this.protocolId = protocolId;
        this.token = token;
        this.userId = userId;
        this.gameId = gameId;
        this.casinoId = casinoId;
        this.currency = currency;
        this.originId = originId;
        this.bonusId = bonusId;

        this.websocket = null;

        this.onclose = this.scene.onError.bind(scene);
        this.onerror = this.scene.onError.bind(scene);
    }

    reset ()
    {
        this.reels = [];

        this.pos = 0;

        this.spinInfo = {};

        this.gameOptions = [];
        this.betsPerLine = [];

        this.units = 0;
        this.minBet = 0;
        this.maxBet = 0;
        this.lastBet = 0      
        this.balance = 0;
        this.balanceReal = 0;
        this.balanceBonus = 0;
        this.currency = "";
        this.yield = "";

        this.currrencyRate = 1;
        this.currrencyDM = 1;//denomination multiplier
        this.currencyPrecision = 2;//precision

        this.restrictedCredits = 0;
        this.nonRestrictedCredits = 0;

        this.realCash = 0;
        this.prmCashable = 0;
        this.prmPlayable = 0;
        this.prmResidual = 0;

        this.bet = 0;

        this.currentJackpotsBeforeJackpotWin = [];
        this.currentJackpots = [];
        this.jackpotWins = [];
        this.jackpotExtraWin = 0;
        this.jackpotSymbols = [];
        this.jackpotCheques = [];
        this.jpwinindex = 0;

        this.jackpotMinbets = [];
        this.jackpotLegalLimit = 0;

        this.totalWin = 0;

        this.totalWinAfterLegalLimit = 0;

        this.combinationId = "";
        this.spinSymbol = 0;
        this.suitSymbol = 0;


        this.cid = 0xA3B5F;
        this.seq = 0;
        this.tid = terminalGetId();

        this.challenge = [ 0, 0 ];

        this.bplIndex = 0;
        
        this.enableJackpotReceivedSendMessage = true;

        this.gameProtocols = [];

        this.extraStartSpinTime = 0;
        this.certifiedVersion = "";
    }

    connect ()
    {
        this.reset();
        
        let socketUrl = "wss://" + this.server + ":" + this.port + "/ws";
        
        this.websocket = new WebSocket( socketUrl );

        this.websocket.binaryType = "arraybuffer";

        this.websocket.onopen = function( ev )
        {
            this.scene.onSocketOpen();
        }.bind(this);

        this.websocket.onclose = function( ev )
        {
            if (this.onclose != null)
            {
                this.onclose(this.scene.getText(GLOBAL_TXT_GAMESOCKET_CONNECTION_CLOSED), "", "", ERR_CONNECTION_CLOSED);
            }
        }.bind(this);

        this.websocket.onmessage = function( ev )
        {
            let msg = ev.data;

            if ( typeof msg != "string" )
            {
                this.parseResponse( new Int32Array( msg ) );
            }
        }.bind(this);

        this.websocket.onerror = function( ev )
        {
            if (this.onerror != null)
            {
                this.onerror(this.scene.getText(GLOBAL_TXT_GAMESOCKET_CONNECTION_ERROR), "", "", ERR_CONNECTION_ERROR);
            }                
        }.bind(this);
    }

    disconnect ()
    {
        this.websocket.close();
    }

    send ( msg )
    {
        if (this.websocket.readyState == WebSocket.OPEN)
        {
			this.websocket.send( msg );
	    }
    }

    sendCmdChallenge ()
    {
        let msg = new ArrayBuffer( 16 );
        let v = new Int32Array( msg );

        v[0] = this.cid; // cid
        v[1] = ++this.seq;
        v[2] = 0x2700; // code
        v[3] = 0; // length

        this.send( msg );
    }

    sendCmdBalance ()
    {
        let msg = new ArrayBuffer( 16 );
        let v = new Int32Array( msg );

        v[0] = this.cid; // cid
        v[1] = ++this.seq;
        v[2] = 2; // code
        v[3] = 0; // length

        this.send( msg );
    }

    sendCmdWalletTokenLogin3 ()
    {
        let tokenPaddedLength = this.token.length + 1;

        while( tokenPaddedLength % 4 )
        {
            ++tokenPaddedLength;
        }

        let userIdPaddedLength = this.userId.length + 1;

        while( userIdPaddedLength % 4 )
        {
            ++userIdPaddedLength;
        }

        let gameIdPaddedLength = this.gameId.length + 1;

        while( gameIdPaddedLength % 4 )
        {
            ++gameIdPaddedLength;
        }

        let currencyLength = (this.currency.length > 4)?4:this.currency.length;

        let externalCurrencyPaddedLength = 4;

        let originIdPaddedLength = this.originId.length + 1;

        while( originIdPaddedLength % 4 )
        {
            ++originIdPaddedLength;
        }

        let bonusIdPaddedLength = this.bonusId.length + 1;

        while( bonusIdPaddedLength % 4 )
        {
            ++bonusIdPaddedLength;
        }
       
        let n = 16 + 20 + 4 + tokenPaddedLength + 4 + userIdPaddedLength + 4 + gameIdPaddedLength + 4 + originIdPaddedLength + 4 + bonusIdPaddedLength + 4 + externalCurrencyPaddedLength;
        let msg = new ArrayBuffer( 16 + n );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;    // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x275C; // 
        v[ 3 ] = n;   // length

        let w = new Uint8Array( msg );

        for ( let i = 0; i < 8; ++i )
        {
            w[ 16 + i ] = this.tid.charCodeAt( i );
        }

        let auth = terminalAuth( this.challenge, this.tid, "" );

        v[ 6 ] = auth[ 0 ];
        v[ 7 ] = auth[ 1 ];


        let offset = 8;
        
        v[ offset ] = this.token.length;

        ++offset;

        for ( var i = 0; i < this.token.length; ++i )
        {
            w[ offset * 4 + i ] = this.token.charCodeAt( i );
        }

        offset += Math.floor( tokenPaddedLength / 4 );

        v[ offset ] = this.userId.length;

        ++offset;

        for ( let i = 0; i < this.userId.length; ++i )
        {
            w[ offset * 4 + i ] = this.userId.charCodeAt( i );
        }

        offset += Math.floor( userIdPaddedLength / 4 );

        v[ offset ] = this.gameId.length;

        ++offset;

        for ( let i = 0; i < this.gameId.length; ++i )
        {
            w[ offset * 4 + i ] = this.gameId.charCodeAt( i );
        }

        offset += Math.floor( gameIdPaddedLength / 4 );

        v[ offset ] = this.casinoId;

        ++offset;

        for ( let i = 0; i < currencyLength; ++i )
        {
            w[ offset * 4 + i ] = this.currency.charCodeAt( i );
        }

        offset += Math.floor( externalCurrencyPaddedLength / 4 );

        let di = getDeviceInfo();

        v[ offset++ ] = di.device_type;
        v[ offset++ ] = di.device_os;
        v[ offset++ ] = di.screen_width;
        v[ offset++ ] = di.screen_height;
        v[ offset++ ] = di.screen_orientation;

        v[ offset ] = this.originId.length;

        ++offset;

        for ( let i = 0; i < this.originId.length; ++i )
        {
            w[ offset * 4 + i ] = this.originId.charCodeAt( i );
        }

        offset += Math.floor( originIdPaddedLength / 4 );

        v[ offset ] = this.bonusId.length;

        ++offset;

        for ( let i = 0; i < this.bonusId.length; ++i )
        {
            w[ offset * 4 + i ] = this.bonusId.charCodeAt( i );
        }

        this.send( msg );
    }

    sendCmdInit ()
    {
        let protocol = this.protocolId +  "\0";

        while( protocol.length % 4 )
        {
            protocol += "\0";
        }
        
        let n = protocol.length;

        let msg = new ArrayBuffer( 16 + n );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;    // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x0001; // 
        v[ 3 ] = n;      // length

        let w = new Uint8Array( msg );

        for ( let i = 0; i < n; ++i )
        {
            w[ 16 + i ] = protocol.charCodeAt( i );
        }

        this.send( msg );
    }

    sendCmdSetUnits ( units )
    {
        let n = 4;
        let msg = new ArrayBuffer( 16 + n );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;    // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x7000; // 
        v[ 3 ] = n;      // length

        v[ 4 ] = units;

        this.send( msg );
    }

    sendCmdGetCurrencyInfo ()
    {
        let currency = this.currency +  "\0";

        while( currency.length % 4 )
        {
            currency += "\0";
        }
        
        let n = currency.length;

        let msg = new ArrayBuffer( 16 + n );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 33 
        v[ 3 ] = n;

        let w = new Uint8Array( msg );

        for ( let i = 0; i < n; ++i )
        {
            w[ 16 + i ] = currency.charCodeAt( i );
        }

        this.send( msg );
    }

    sendCmdGetReels ()
    {
        let msg = new ArrayBuffer( 16 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;    // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x4003; // 
        v[ 3 ] = 0;      // length

        this.send( msg );
    }

    sendCmdCoinrainJackpot ()
    {
        let msg = new ArrayBuffer(16);

        let v = new Int32Array(msg);

        v[0] = this.cid;    // cid
        v[1] = ++this.seq;
        v[2] = 0x7101; // cmd query coinrain jackpot
        v[3] = 0;      // length

        this.send(msg);
    }

    sendCmdGetCoinrainMinbet ()
    {
        let msg = new ArrayBuffer(16);

        let v = new Int32Array(msg);

        v[0] = this.cid;
        v[1] = ++this.seq;
        v[2] = 0x7103;// cmd query coinrain minbet
        v[3] = 0;

        this.send(msg);
    }

    sendCmdGetCoinrainLegalLimit ()
    {
        let msg = new ArrayBuffer(16);

        let v = new Int32Array(msg);

        v[0] = this.cid;
        v[1] = ++this.seq;
        v[2] = 43;// cmd query coinrain legal limit
        v[3] = 0;

        this.send(msg);
    }

    sendCmdGetProtocolOptions ()
    {
        let opt = Array();

        opt[0] = "denoms";
        opt[1] = "max_total_bet";
        opt[2] = "bets_per_line";
        opt[3] = "yield";
        opt[4] = "preferred_bet";
        opt[5] = "extra_start_spin_time";
        opt[6] = "certified_version";

        let n = 0;
        let l = 0;

        for ( let i = 0; i < opt.length; ++i )
        {
            n = opt[i].length;
        
            if ( (n % 4) == 0 )
            {
                n += 4;
            }
            else
            {
                while( (n % 4) != 0 )
                {
                    ++n;
                }
            }

            l += n;
        }

        let msg = new ArrayBuffer( 20 + opt.length * 4  +  l );

        let v = new Int32Array( msg );

        v[0] = this.cid; // cid
        v[1] = ++this.seq;
        v[2] = 8; // cmd get protocol options
        v[3] = 4 + opt.length * 4  +  l; // length
        v[4] = opt.length;

        let j = 5;

        let w = new Uint8Array( msg );

        for ( let i = 0; i < opt.length; ++i )
        {
            v[j] = opt[i].length;

            let len = opt[i].length;
        
            if ( (len % 4) == 0 )
            {
                len += 4;
            }
            else
            {
                while( (len % 4) != 0 )
                {
                    ++len;
                }
            }

            for ( let k = 0; k < opt[i].length; ++k )
            {
                w[ ( j+1 ) * 4 + k ] = opt[i].charCodeAt( k );
            }

            for ( let k = opt[i].length; k < len; ++k )
            {
                w[ ( j + 1 ) * 4 + k ] = 0;
            }

            j += 1 + len / 4; 
        }

        this.send( msg );
    }

    sendCmdSpin ()
    {
        let msg = new ArrayBuffer( 20 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x4001;
        v[ 3 ] = 4;

        v[ 4 ] = this.units * this.betsPerLine[ this.bplIndex ];
        
        this.send( msg );
    }

    sendCmdCashout ()
    {
        let msg = new ArrayBuffer( 20 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x4002;
        v[ 3 ] = 4;
        v[ 4 ] = this.units * this.betsPerLine[ this.bplIndex ];

        this.send( msg );
    }
    
    sendCmdGetState (bet = this.lastBet)
    {
        let msg = new ArrayBuffer( 20 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x4004;
        v[ 3 ] = 4;
        v[ 4 ] = bet;

        this.send( msg );
    }

    sendCmdGetLastBet ()
    {
        let msg = new ArrayBuffer( 16 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid;
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x4005;
        v[ 3 ] = 0;

        this.send( msg );
    }

    sendCmdEvent ( str )
    {
        let date = new Date();
        let timestamp = Math.floor(date.getTime()/1000);
        
        let gamemodule = this.protocolId +  "\0";

        while( gamemodule.length % 4 )
        {
            gamemodule += "\0";
        }
        
        while( str.length % 4 )
        {
            str += "\0";
        }

        let n = 8 + gamemodule.length + str.length;

        let msg = new ArrayBuffer( 28 + n );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid; // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 13; // cmd_event
        v[ 3 ] = 12 + n; // length
        v[ 4 ] = timestamp;     

        v[5] = gamemodule.length;

        let w = new Uint8Array( msg );

        for ( let i = 0; i < gamemodule.length; ++i )
        {
            w[ 24 + i ] = gamemodule.charCodeAt( i );
        }

        let p = 6 + gamemodule.length / 4;

        v[p++] = 0;
        v[p++] = str.length;

        for ( let i = 0; i < str.length; ++i )
        {
            w[ p * 4 + i ] = str.charCodeAt( i );
        }

        v[p + str.length / 4] = 0;

//        this.send( msg );
    }

    sendCmdGetGames ()
    {
        let msg = new ArrayBuffer( 16 );

        let v = new Int32Array( msg );

        v[ 0 ] = this.cid; // cid
        v[ 1 ] = ++this.seq;
        v[ 2 ] = 0x272A;
        v[ 3 ] = 0; // length

        this.send( msg );
    }

    responseBalance ( msg )
    {
        let balanceOld = this.balance;
        let balanceRealOld = this.balanceReal;
        let balanceBonusOld = this.balanceBonus;

        this.balance = msg[ 4 ];
        this.currency = currencyCode( msg[ 5 ] );

        this.restrictedCredits = msg[ 6 ];
        this.nonRestrictedCredits = msg[ 7 ];

        // new in server version 1.2.517:
        this.realCash = msg[ 8 ];
        this.prmCashable = msg[ 9 ];
        this.prmPlayable = msg[ 10 ];
        this.prmResidual = msg[ 11 ];

        let reason = msg[ 12 ];

        this.balanceReal = this.realCash + this.prmCashable;
        this.balanceBonus = this.prmPlayable + this.prmResidual;

        if (balanceOld != this.balance && this.units > 0)
        {
            this.scene.balanceChange(this.balance, Math.floor(balanceOld), this.balanceReal, balanceRealOld, this.balanceBonus, balanceBonusOld);
        }

        this.sendCmdGetCurrencyInfo();
    }

    responseUnitsSet ( msg )
    {
        this.currency = currencyCode( msg[ 4 ] );
        this.units = msg[ 5 ];
        this.minBet = msg[ 6 ];
        this.maxBet = msg[ 7 ];

        this.sendCmdCoinrainJackpot();
        this.sendCmdGetCoinrainMinbet();
        this.sendCmdGetCoinrainLegalLimit();
        this.sendCmdGetLastBet();

        let message = "Logged in; balance: " + formatValue(this.balance, this.currencyPrecision) + " + " + formatValue(this.restrictedCredits + this.nonRestrictedCredits, this.currencyPrecision);
        this.sendCmdEvent( message );

        message = "Units set: " + formatValue(this.units, this.currencyPrecision) + ", " + this.currency;
        this.sendCmdEvent( message );

        this.scene.balanceChange(this.balance, 0, this.balanceReal, 0, this.balanceBonus, 0);
    }

    responseCurrencyInfo ( msg)
    {
        let currency = currencyCode( msg[ 4 ] );
        
        this.currrencyRate = msg [5];
        this.currrencyDM = msg [6];
        this.currencyPrecision = msg [7];
    }

    responseChallenge ( msg )
    {
        this.challenge = [ msg[ 4 ], msg[ 5 ] ];
    	
        this.sendCmdWalletTokenLogin3();
    }

    responseLoginSuccessful ()
    {
        this.sendCmdBalance();
        this.sendCmdInit();
    }
    
    responseInit ()
    {
        this.sendCmdGetProtocolOptions ();
    }

    responseWarning ( str, msg )
    {
        let code = new Long(msg[ 2 ], 0, true);
        console.log("warning: " + str + ", Error Code " + code.low);
    }

    responseError ( str, msg )
    {
        let code = new Long(msg[ 2 ], 0, true);

        if (code.low != -0x4002)
        {
            responseError ( str, msg, this.scene );
        }
        else
        {
            responseErrorCashout ( str, msg, this.scene, code );
        }
    }

    reponseReels ( msg )
    {
        this.reels.length = 0;
        let pos = 4;
    
        let rsize = 0;
    
        for ( let i = 0; i < 1; ++i )
        {
            rsize = msg[ pos++ ];
    
            let reel = [];

            for ( let j = 0; j < rsize; ++j )
            {
                reel[j] = msg[ pos + j ];
            }
            
            this.reels.push(reel);
        }

        //this is last initialization step, notify main scene that game session is started
        this.scene.onGameSessionReady();
    }

    responseState ( msg )
    {
        let pos = 4;

        this.bet = msg[ pos++ ];

        this.bet /= this.units;

        let betFound = false;        

        for (let i = 0; i < this.betsPerLine.length; ++i)
        {
            if (this.bet == this.betsPerLine[i])
            {
                this.bplIndex = i;
                betFound = true;
                break;
            }   
        }
        
        this.spinInfo.newState = [0, 0, 0, 0, 0, 0];
        
        if (betFound)
        {
            this.spinInfo.newState[0] = msg[ pos++ ];//cashbox
            this.spinInfo.newState[1] = msg[ pos++ ];//progresive bonus
            this.spinInfo.newState[2] = msg[ pos++ ];//suits ...
            this.spinInfo.newState[3] = msg[ pos++ ];
            this.spinInfo.newState[4] = msg[ pos++ ];
            this.spinInfo.newState[5] = msg[ pos++ ];
        }

        this.scene.stateReceive();
    }

    responseLastBet ( msg )
    {
        this.lastBet = msg[4];

        if (this.lastBet == 0)
        {
            this.lastBet = this.units * this.betsPerLine[ this.bplIndex ];
        }

        this.sendCmdGetState(this.lastBet, true);
        this.sendCmdGetReels();
    }

    responseCoinrainMinbet( msg )
    {
        this.jackpotMinbets.length = 0;

        let pos = 4;
        let n = msg[pos++];

        for (let i = 0; i < n; ++i)
        {
            this.jackpotMinbets[i] = msg[pos++];
        }
    }

    responseCoinrainLegalLimit( msg )
    {
        let pos = 4;
        this.jackpotLegalLimit = msg[ pos++ ];
    }

    responseCoinrainJackpot ( msg )
    {
        this.currentJackpots.length = 0;
        let pos = 4;
        let n = msg[pos++];

        for (let i = 0; i < n; ++i)
        {
            this.currentJackpots[i] = msg[pos++];
        }

        let message = "Current jackpots:\n";

        if (this.currentJackpots[0] !== undefined)
        {
            message += "- grand: " + formatValue(this.currentJackpots[0], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[1] !== undefined)
        {
            message += "- major: " + formatValue(this.currentJackpots[1], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[2] !== undefined)
        {
            message += "- minor: " + formatValue(this.currentJackpots[2], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[3] !== undefined)
        {
            message += "- mini: " + formatValue(this.currentJackpots[3], this.currencyPrecision) + "\n";
        }

        if (this.enableJackpotReceivedSendMessage)
        {
            this.sendCmdEvent (message);
            this.enableJackpotReceivedSendMessage = false;
        }
    }

    responseV2CoinrainJackpot ( msg )
    {
        this.currentJackpotsBeforeJackpotWin.length = 0;
        this.currentJackpots.length = 0;
        this.jackpotWins.length = 0;

        let pos = 4;
        let n = msg[ pos++ ];//4 jackpots, current jackpots after this spin, before jackpot win 

        for (let i = 0; i < n; ++i)
        {
            this.currentJackpotsBeforeJackpotWin[i] = msg[ pos++ ];
        }

        this.jackpotWins = [0, 0, 0, 0];

        this.jpwinindex = msg[ pos++ ];
        
        if (this.jpwinindex > 0)
        {
            this.jackpotWins[this.jpwinindex - 1] = msg[ pos++ ];
            
            this.jackpotExtraWin = msg[ pos++ ];

            let nd = msg[ pos++ ]

            this.jackpotSymbols = [];
            
            for (let i = 0; i < nd; ++i)
            {
                let sym = msg[ pos++ ];
                this.jackpotSymbols.push(sym);
            }

            let nc = msg[ pos++ ]

            this.jackpotCheques = [];

            for (let i = 0; i < nc; ++i)
            {
                let c = msg[ pos++ ];
                this.jackpotCheques.push(c);
            }
        }
        else
        {
            pos += 4;
        }

        n = msg[ pos++ ];//4 jackpots, current jackpots after this spin, after jackpot win

        for (let i = 0; i < n; ++i)
        {
            this.currentJackpots[i] = msg[ pos++ ];
        }


        if (this.enableJackpotReceivedSendMessage)
        {
            this.sendJackpotV2CoinrainEvent();
        }
    }

    responseProtocolOptions ( msg )
    {
        this.gameOptions.length = 0;

        let n = msg[4];
        let pos = 5;
        
        let i, j;

        for ( i = 0; i < n; ++i )
        {
            let key = "";
            let value = "";

            let len = msg[pos];

            if ( (len % 4) == 0 )
            {
                len += 4;
            }
            else
            {
                while( (len % 4) != 0 )
                {
                    ++len;
                }
            }
            
            for ( j = 0; j < len / 4; ++j  )
            {
                key += convert( msg[pos + 1 + j].toString(16) );
            }
            
            pos += 1 + len / 4;

            len = msg[pos];

            if ( (len % 4) == 0 )
            {
                len += 4;
            }
            else
            {
                while( (len % 4) != 0 )
                {
                    ++len;
                }
            }
            
            for ( j = 0; j < len / 4; ++j  )
            {
                value += convert( msg[pos + 1 + j].toString(16) );
            }
            
            pos += 1 + len / 4;
            this.gameOptions[key] = value;
        }

        this.betsPerLine = this.gameOptions["bets_per_line"].split(" ").map (Number);
        let preferredBet = parseInt(this.gameOptions["preferred_bet"]);

        if (Number.isInteger(preferredBet))
        {
            let index = this.betsPerLine.indexOf(preferredBet);

            if (index != -1)
            {
                this.bplIndex = index;
            }
        }

        let extraStartSpinTime = parseInt(this.gameOptions["extra_start_spin_time"]);

        if (Number.isInteger(extraStartSpinTime) && extraStartSpinTime > 0)
        {
            this.extraStartSpinTime = extraStartSpinTime;
        }

        this.yield = this.gameOptions["yield"];

        let denoms = this.gameOptions["denoms"].split(" ");

        if ( denoms.length == 0 || denoms[0] == "" )
        {
            this.scene.onError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_INVALID_SETTINGS_ERROR), "", "", -1);
        }
        else
        {
            this.sendCmdSetUnits(parseFloat(denoms[0]) * 100);
        }

        if (this.gameOptions["certified_version"] !== "")
        {
            this.certifiedVersion = this.gameOptions["certified_version"];
        }
	}

    responseGames ( msg )
    {
        this.gameProtocols = [];

        let n = msg[4];//total games
        let pos = 0;
        
        let payload = msg.slice(5);

        for (let i = 0; i < n; ++i)
        {
            let ci = 0;//current item
            
            let j;

            while(true)
            {
                if (ci == 4)
                {
                    ++pos;//category id

                    let nd = payload[pos++];//number of denoms
                    pos += nd;
                    break;
                }

                let item = "";//protocol id, dll, game name and image
                
                let len = payload[pos++];

                if ( (len % 4) == 0 )
                {
                    len += 4;
                }
                else
                {
                    while( (len % 4) != 0 )
                    {
                        ++len;
                    }
                }
                
                for ( j = 0; j < len / 4; ++j  )
                {
                    item += convert( payload[pos + j].toString(16) );
                }
        
                if (ci == 0)
                {
                    this.gameProtocols.push(item);//first item is game protocol id
                }
                
                pos = pos + j;
                ++ci;
            }                
        }            
    }

    responseSpin (msg)
    {
        this.spinInfo = {};

        this.pos = 4;
        this.bet = msg[ this.pos++ ];

        this.spinInfo.oldState = [0, 0, 0, 0, 0, 0];
        this.spinInfo.newStateBeforeWins = [0, 0, 0, 0, 0, 0];
        this.spinInfo.newState = [0, 0, 0, 0, 0, 0];

//old state        
        this.spinInfo.oldState[0] = msg[ this.pos++ ];//cashbox
        this.spinInfo.oldState[1] = msg[ this.pos++ ];//prog bonus
        this.spinInfo.oldState[2] = msg[ this.pos++ ];//suits
        this.spinInfo.oldState[3] = msg[ this.pos++ ];
        this.spinInfo.oldState[4] = msg[ this.pos++ ];
        this.spinInfo.oldState[5] = msg[ this.pos++ ];

        this.spinInfo.spinSymbol = msg[ this.pos++ ];
        this.spinInfo.suitSymbol = msg[ this.pos++ ];

        this.spinSymbol = this.spinInfo.spinSymbol;//for cashout history 
        this.suitSymbol = this.spinInfo.suitSymbol;

        let combinationId = new Long(msg[ this.pos ], msg[ this.pos + 1 ], true);

        ++this.pos;
        ++this.pos;

        this.spinInfo.combinationId = combinationId.toString();
        this.combinationId = this.spinInfo.combinationId;

        this.spinInfo.newStateBeforeWins[0] = msg[ this.pos++ ];//cashbox
        this.spinInfo.newStateBeforeWins[1] = msg[ this.pos++ ];//prog bonus
        this.spinInfo.newStateBeforeWins[2] = msg[ this.pos++ ];//suits
        this.spinInfo.newStateBeforeWins[3] = msg[ this.pos++ ];
        this.spinInfo.newStateBeforeWins[4] = msg[ this.pos++ ];
        this.spinInfo.newStateBeforeWins[5] = msg[ this.pos++ ];

        this.spinInfo.forcedCashout = msg[ this.pos++ ];
        this.spinInfo.bonusWin = msg[ this.pos++ ];

//new state        
        this.spinInfo.newState[0] = msg[ this.pos++ ];//cashbox
        this.spinInfo.newState[1] = msg[ this.pos++ ];//prog bonus
        this.spinInfo.newState[2] = msg[ this.pos++ ];//suits
        this.spinInfo.newState[3] = msg[ this.pos++ ];
        this.spinInfo.newState[4] = msg[ this.pos++ ];
        this.spinInfo.newState[5] = msg[ this.pos++ ];

        this.totalWin = msg[ this.pos++ ];

        this.totalWinAfterLegalLimit = this.totalWin;

        this.balance = msg[ this.pos++ ];

        // new in server version 1.2.517:
        this.realCash = msg[ this.pos++ ];
        this.prmCashable = msg[ this.pos++ ];
        this.prmPlayable = msg[ this.pos++ ];
        this.prmResidual = msg[ this.pos++ ];

        this.balanceReal = this.realCash + this.prmCashable;
        this.balanceBonus = this.prmPlayable + this.prmResidual;

       	// new in server version 1.2.574:

        if (this.pos < msg.length)
        {
            this.totalWinAfterLegalLimit = msg[ this.pos++ ];
        }

        this.spinInfo.legalLimitApplied = (this.totalWin != this.totalWinAfterLegalLimit);

        this.spinInfo.balance = this.balance;
        this.spinInfo.bet = this.bet;
        this.spinInfo.win = this.totalWinAfterLegalLimit;
        this.spinInfo.bpl = this.getBetPerLine();
        this.spinInfo.units = this.getUnits();

        this.scene.spinReceive();
    }

    responseCashout (msg)
    {
        this.spinInfo = {};

        this.pos = 4;
        this.bet = msg[ this.pos++ ];

        this.spinInfo.oldState = [0, 0, 0, 0, 0, 0];
        this.spinInfo.newState = [0, 0, 0, 0, 0, 0];

//old state        
        this.spinInfo.oldState[0] = msg[ this.pos++ ];//cashbox
        this.spinInfo.oldState[1] = msg[ this.pos++ ];//prog bonus
        this.spinInfo.oldState[2] = msg[ this.pos++ ];//suits
        this.spinInfo.oldState[3] = msg[ this.pos++ ];
        this.spinInfo.oldState[4] = msg[ this.pos++ ];
        this.spinInfo.oldState[5] = msg[ this.pos++ ];

        this.spinInfo.combinationId = this.combinationId;//for spin history
        this.spinInfo.spinSymbol = this.spinSymbol;
        this.spinInfo.suitSymbol = this.suitSymbol;

//new state        
        this.spinInfo.newState[0] = msg[ this.pos++ ];//cashbox
        this.spinInfo.newState[1] = msg[ this.pos++ ];//prog bonus
        this.spinInfo.newState[2] = msg[ this.pos++ ];//suits
        this.spinInfo.newState[3] = msg[ this.pos++ ];
        this.spinInfo.newState[4] = msg[ this.pos++ ];
        this.spinInfo.newState[5] = msg[ this.pos++ ];

        this.totalWin = msg[ this.pos++ ];
        this.totalWinAfterLegalLimit = this.totalWin;

        this.balance = msg[ this.pos++ ];

        // new in server version 1.2.517:
        this.realCash = msg[ this.pos++ ];
        this.prmCashable = msg[ this.pos++ ];
        this.prmPlayable = msg[ this.pos++ ];
        this.prmResidual = msg[ this.pos++ ];

        this.balanceReal = this.realCash + this.prmCashable;
        this.balanceBonus = this.prmPlayable + this.prmResidual;

       	// new in server version 1.2.574:

        if (this.pos < msg.length)
        {
            this.totalWinAfterLegalLimit = msg[ this.pos++ ];
        }

        this.spinInfo.legalLimitApplied = (this.totalWin != this.totalWinAfterLegalLimit);

        this.spinInfo.balance = this.balance;
        this.spinInfo.bet = 0;
        this.spinInfo.win = this.totalWinAfterLegalLimit;
        this.spinInfo.bpl = this.getBetPerLine();
        this.spinInfo.units = this.getUnits();
        
        this.scene.cashoutReceive();
    }

    sendSpinReceivedEvent ()
    {
        let message = "Units: " + formatValue(this.units, this.currencyPrecision) + "\n";
        message += "Total bet: " +  formatValue(this.units * this.betsPerLine[ this.bplIndex ], this.currencyPrecision) + "\n";
        message += "Bet: " + formatValue(this.bet, this.currencyPrecision) + "\n";

        message += "Symbol: " + SYMBOLS[this.spinInfo.spinSymbol] + "(" + this.spinInfo.spinSymbol + ")\n";
       
        if (this.totalWin > 0)
        {
            message += "Total win: " + formatValue(this.totalWin, this.currencyPrecision) + "\n";
        }

        message += "Balance: " + formatValue(this.balance, this.currencyPrecision) + "\n";

        this.sendCmdEvent( message );
    }

    sendJackpotV2CoinrainEvent()
    {
        let message = "";

        if (this.getJackpotWin() > 0)
        {
            message = "Jackpots win: \n";

            if (this.jackpotWins[0] !== undefined && this.jackpotWins[0] > 0)
            {
                message += "- grand: " + formatValue(this.jackpotWins[0], this.currencyPrecision) + "\n";
            }

            if (this.jackpotWins[1] !== undefined && this.jackpotWins[1] > 0)
            {
                message += "- major: " + formatValue(this.jackpotWins[1], this.currencyPrecision) + "\n";
            }

            if (this.jackpotWins[2] !== undefined && this.jackpotWins[2] > 0)
            {
                message += "- minor: " + formatValue(this.jackpotWins[2], this.currencyPrecision) + "\n";
            }

            if (this.jackpotWins[3] !== undefined && this.jackpotWins[3] > 0)
            {
                message += "- mini: " + formatValue(this.jackpotWins[3], this.currencyPrecision) + "\n";
            }
        }

        message += "Current jackpots:\n";

        if (this.currentJackpots[0] !== undefined)
        {
            message += "- grand: " + formatValue(this.currentJackpots[0], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[1] !== undefined)
        {
            message += "- major: " + formatValue(this.currentJackpots[1], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[2] !== undefined)
        {
            message += "- minor: " + formatValue(this.currentJackpots[2], this.currencyPrecision) + "\n";
        }

        if (this.currentJackpots[3] !==undefined)
        {
            message += "- mini: " + formatValue(this.currentJackpots[3], this.currencyPrecision) + "\n";
        }

        this.sendCmdEvent(message);

        this.enableJackpotReceivedSendMessage = false;
    }

    parseResponse ( msg )
    {
        switch( msg[ 2 ] )
        {
        case 2:
    
            this.responseBalance( msg );
            break;

        case 3:

            this.responseUnitsSet( msg );
            break;
        
        case 0x2700:
    
            this.responseChallenge( msg );
            break;
    
        case 0x2701:
    
            this.responseLoginSuccessful( msg );
            break;
        
        case 1:
    
            this.responseInit();
            break;
    
        case 8:
        
            this.responseProtocolOptions( msg );
            break;
        
        case 33:
            
            this.responseCurrencyInfo( msg );    
            break;
        
        case 43:

            this.responseCoinrainLegalLimit( msg );
            break;

        case 0x4001:
            
            this.responseSpin( msg );
            break;
    
        case 0x4002:

            this.responseCashout( msg );
            break;

        case 0x4003:
    
            this.reponseReels( msg );
            break;

        case 0x4004:
    
            this.responseState( msg );
            break;

        case 0x4005:
    
            this.responseLastBet( msg );
            break;

        case 0x272A:
            
            this.responseGames( msg );
            break;

        case 0x7101:

            this.responseCoinrainJackpot( msg );
            break;

        case 0x7103:

            this.responseCoinrainMinbet( msg );
            break;

        case 0x7023:

            this.responseV2CoinrainJackpot( msg );
            break;
    
        case -1:
    
            this.responseWarning( "UNKNOWN PACKET", msg );
            break;
    
        case -2:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_BAD_PACKET_ERROR), msg );
            break;
    
        case -3:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_NOT_INITIALIZED_ERROR), msg );
            break;
    
        case -4:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_HANDLER_NAME_UNKNOWN_ERROR), msg );
            break;
    
        case -5:
            
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_NOT_ENOUGH_CREDIT_ERROR), msg );
            break;
    
        case -6:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_INVALID_UNITS_ERROR), msg );
            break;
    
        case -7:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_UNITS_NOT_SET_ERROR), msg );
            break;
    
        case -8:
    
            this.responseWarning( "Units already set", msg );
            break;
    
        case -9:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_INVALID_BET_ERROR), msg );
            break;
    
        case -10:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_SERVER_ERROR), msg );
            break;
    
        case -11:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_NOT_LOGGED_IN_ERROR), msg );
            break;
    
        case -12:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_COMMAND_NOT_ALLOWED_ERROR), msg );
            break;
    
        case -13:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_GAME_DISABLED_ERROR), msg );
            break;
    
        case -14:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_GAME_NOT_CONFIGURED_ERROR), msg );
            break;
    
        case -15:
    
            this.responseWarning( "Game not in tournament", msg );
            break;
    
        case -16:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_INVALID_LINES_ERROR), msg );
            break;
    
        case -17:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_INVALID_POSITION_ERROR), msg );
            break;
    
        case -0x2701:
    
                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_LOGIN_FAILED_ERROR), msg );
            break;

        case -0x4002:

                this.responseError(this.scene.getText(GLOBAL_TXT_GAMESOCKET_LF_CASHOUT_FAILED), msg );
            break;
        }
    }

    betChange ()
    {
        if ( ++this.bplIndex >= this.betsPerLine.length || this.betsPerLine[ this.bplIndex ] > this.maxBet )
        {
            this.bplIndex = 0;
        }
    }

    betChange2 ()
    {
        if ( (this.bplIndex + 1) >= this.betsPerLine.length)
        {
            return;
        }

        if ( this.betsPerLine[ this.bplIndex + 1 ] > this.maxBet )
        {
            return;
        }

        ++this.bplIndex;
    }

    getBetPerLine ()
    {
        return this.betsPerLine[ this.bplIndex ];
    }

    getBet ()
    {
        return (this.betsPerLine[ this.bplIndex ] * this.units);
    }

    getBetByIndex (bplIndex)
    {
        return (this.betsPerLine[ bplIndex ] * this.units);
    }

    setBet (bplIndex)
    {
        if ( bplIndex >= this.betsPerLine.length || this.betsPerLine[ bplIndex ] > this.maxBet )
        {
            this.bplIndex = 0;
        }
        else
        {
            this.bplIndex = bplIndex;
        }
    }

    getMinBet ()
    {
        return (this.betsPerLine[0] * this.units);
    }

    getMaxBet ()
    {
        let bpl = 0;

        for (let i = 0; i < this.betsPerLine.length; ++i)
        {
            if (this.betsPerLine[i] > this.maxBet)
            {
                return this.betsPerLine[bpl] * this.units;
            }
            else
            {
                bpl = i;
            }
        }

        return this.betsPerLine[bpl] * this.units;
    }

    setMaxBet ()
    {
        for ( let i = 0; i < this.betsPerLine.length; ++i)
        {
            if (this.betsPerLine[i] > this.maxBet)
            {
                return;
            }
            else
            {
                this.bplIndex = i;
            }
        }
    }

    getBets ()
    {
        let bets = [];

        for ( let i = 0; i < this.betsPerLine.length; ++i)
        {
            if (this.betsPerLine[i] <= this.maxBet)
            {
                bets.push(formatValue(this.betsPerLine[ i ] * this.units, this.currencyPrecision));
            }
            else
            {
                break;
            }
        }

        return bets;
    }

    getLimits ()
    {
        let bets = [];

        for ( let i = 0; i < this.betsPerLine.length; ++i)
        {
            if (this.betsPerLine[i] <= this.maxBet)
            {
                bets.push(formatValue(this.betsPerLine[ i ] * this.units * LIMIT_MULTIPLIER, this.currencyPrecision));
            }
            else
            {
                break;
            }
        }

        return bets;
    }

    getBetSel ()
    {
        return this.bplIndex;
    }

    getBalance ()
    {
        return (this.units > 0)? this.balance:0;
    }

    getBalanceBeforeWin ()
    {
        let w = this.getTotalWin();

        for (let i = 0; i < this.jackpotWins.length; ++i)
        {
            w += this.jackpotWins[i];
        }

        w += this.jackpotExtraWin;

        return (this.units > 0)? (this.balance - w):0;
    }

    getBalanceReal ()
    {
        return (this.units > 0)? this.balanceReal:0;
    }

    getBalanceRealBeforeWin ()
    {
        let w = this.getTotalWin();

        for (let i = 0; i < this.jackpotWins.length; ++i)
        {
            w += this.jackpotWins[i];
        }

        w += this.jackpotExtraWin;

        return (this.units > 0)? (this.balanceReal - w):0;
    }

    getBalanceBonus ()
    {
        return this.balanceBonus;
    }

    getTotalWin ()
    {
        if (this.totalWinAfterLegalLimit < this.totalWin)
        {
            return this.totalWinAfterLegalLimit;
        }
        else
        {
            return this.totalWin;
        }
    }

    getRealCash ()
    {
        return this.realCash;
    }
    
    getPrmCashable ()
    {
        return this.prmCashable;
    }

    getPrmPlayable ()
    {
        return this.prmPlayable;
    }
    
    getPrmResidual ()
    {
        return this.prmResidual;
    }

    getJackpotsBeforeJackpotWin()
    {
        return this.currentJackpotsBeforeJackpotWin;
    }

    getJackpots ()
    {
        return this.currentJackpots;
    }

    isJackpotEnable ()
    {
        let w = 0;

        for (let i = 0; i < this.currentJackpots.length; ++i)
        {
            w += this.currentJackpots[i];
        }

        return w > 0;
    }

    getUnits ()
    {
        return this.units;
    }

    getCurrency ()
    {
        return this.currency;
    }

    getCurrencyDM ()
    {
        return this.currrencyDM;
    }

    getCurrencyPrecision ()
    {
        return this.currencyPrecision;
    }

    getReels ()
    {
        return this.reels;
    }

    getYield ()
    {
        return this.yield;
    }

    getJackpotWin ()
    {
        let w = 0;

        for (let i = 0; i < this.jackpotWins.length; ++i)
        {
            w += this.jackpotWins[i];
        }

        return w;
    }

    getJackpotExtraWin ()
    {
        return this.jackpotExtraWin;
    }

    getJackpotSymbols ()
    {
        return this.jackpotSymbols;
    }

    getJackpotCheques ()
    {
        return this.jackpotCheques;
    }

    getGameProtocols ()
    {
        return this.gameProtocols;
    }

    getJackpotWins ()
    {
        return this.jackpotWins;
    }
    
    getJackpotMinbet (v)
    {
        return this.jackpotMinbets[v];
    }

    getJackpotMinbets ()
    {
        return this.jackpotMinbets;
    }

    getJackpotLegalLimit ()
    {
        return this.jackpotLegalLimit;
    }
    
    getGame ()
    {
        return this.spinInfo;
    }

    queryBalance ()
    {
        this.sendCmdBalance();
    }

    queryJackpots ()
    {
        this.sendCmdCoinrainJackpot();
    }

    clearJackpotWin ()
    {
        this.jpwinindex = 0;
    }

    getExtraStartSpinTime ()
    {
        return this.extraStartSpinTime;
    }

    getCertifiedVersion ()
    {
        return this.certifiedVersion;
    }

    spin ()
    {
        this.jackpotWins = [];

        this.jackpotExtraWin = 0;
        this.jackpotSymbols = [];
        this.jackpotCheques = [];

        this.enableJackpotReceivedSendMessage = true;

        this.sendCmdSpin();
    }
}