//  *********************************************************
//  Configuration Section - Begin
//  *********************************************************

"use strict";
var serverConnectorVersionStr = "5.4.4";
var serverConnectorVersionNum = 50404.010;
var mongoStatus = "Running";
var connectorPort = 3014;

var sslEnabled       = false;
var cryptoEnabled    = true; 
var sslPrivateKey    = "/etc/pki/tls/private/appx.key";
var sslCertificate   = "/etc/pki/tls/certs/appx.com.crt";
var sslCertAuthority = "/etc/pki/tls/certs/gd_bundle.crt";

var mongoDatabase    = "AppxDatabaseCache";
var mongoHost        = "localhost";
var mongoPort        = 27017;

var appxdebug        = false;
var appxlog          = false;

//  *********************************************************
//  Configuration Section - End
//  *********************************************************

// Library Imports

var StringLib   = require('string');
var GridStore   = require('mongodb').GridStore;
var MongoClient = require('mongodb').MongoClient;
var MongoServer = require('mongodb').Server;
var fs          = require('fs');

// Global variabls and Objects

var mongoClient = new MongoClient(new MongoServer(mongoHost, mongoPort));
var mongoUrl    = 'mongodb://'+mongoHost+':'+mongoPort+'/'+mongoDatabase;
var mongoCacheDb = null;

var initDatabase = function () {
    try {
	mongoCacheDb.dropDatabase();
    } catch (e) {
	console.log("ERROR: failed to initialize mongoDatabase, err="+e);
	console.log(e.stack);
    }
};

mongoClient.connect(mongoUrl,function (err, db) {
    mongoCacheDb = db;
    initDatabase();
    if (err){
	mongoStatus = "Error";
    }
});

var arrayPush = Array.prototype.push;

// If there is an extra argument on the command line that is a request to change our working directory

if (process.argv.length > 2)
    process.chdir(process.argv[2]);
else if( process.env.APPX_CONNECTOR_DIR )
    process.chdir(process.env.APPX_CONNECTOR_DIR);

// Find out which result Buffer.toJSON() returns.  This changed between Node 11.0 and 11.1
var appxToJsonTest = new Buffer("test");
var appxIsToJsonAnArray = appxToJsonTest.toJSON(appxToJsonTest) instanceof Array;

// create a log file
if (appxlog) {
    var d = new Date();
    var logfile = d.getMonth() + 1 + '' + d.getDate() + '' + d.getFullYear() + '' + d.getHours() + '' + d.getMinutes() + '' + d.getSeconds() + '' + '.log';
    fs.appendFile(logfile, 'creating the logfile', function (err) {
        if (err) {
            console.log("Log File Error:  " + err);
        }
        console.log('Created log file:  ' + logfile);
    });
}


var buildMongoSelection = function(gridRequest) {
    var selection = [];
    var result = {};
    if( gridRequest["_search"] == "true" ) {
	var filters = JSON.parse(gridRequest.filters);
	var groupOp = "$" + filters.groupOp.toLowerCase();
	var rules = filters.rules;
	
	for( var i=0; i<rules.length; i++ ) {
	    var rule = rules[i];
	    var field = rule.field;
	    var op = rule.op;
	    var data = rule.data;
	    var mongo = {};
	    var datatype = field.slice(-1);

	    switch(datatype) {
		 case "I": data = parseInt(data); break;
		 case "F": data = parseFloat(data); break;
		 case "B": data == "Y"; break;
	    }

	    mongo[field] = {};
	    
	    /* Test for uppercase letters in search string. If search string
	    ** contains uppercase then we do case sensitive search. Otherwise
	    ** searches are case insensitive.*/
	    if (/[A-Z]/.test(data) || !isNaN(data)){ 
		switch( op ) {
		     case 'eq': mongo[field]["$eq"] = data; break;
		     case 'ne': mongo[field]["$ne"] = data; break;
		     case 'lt': mongo[field]["$lt"] = data; break;
		     case 'gt': mongo[field]["$gt"] = data; break;
		     case 'le': mongo[field]["$lte"] = data; break;
		     case 'ge': mongo[field]["$gte"] = data; break;
		     case 'nu': mongo[field]["$eq"] = " "; break;
		     case 'nn': mongo[field]["$ne"] = " "; break;
		     case 'bw': mongo[field]["$regex"] = "^"+data; break;
		     case 'ew': mongo[field]["$regex"] = data+"$"; break;
		     case 'cn': mongo[field]["$regex"] = data; break;
		     case 'bn': mongo[field]["$regex"] = "^(?!"+data+").*"; break;
		     case 'en': mongo[field]["$regex"] = ".*(?<!"+data+")$"; break;
		     case 'nc': mongo[field]["$regex"] = "^((?!"+data+").)*$"; break;
		}
	    } else{
		switch( op ) {
		     case 'eq': mongo[field]["$regex"] = "^("+data+")$"; break;
		     case 'ne': mongo[field]["$regex"] = "^(?!"+data+")$"; break;
		     case 'lt': mongo[field]["$lt"] = data; break;
		     case 'gt': mongo[field]["$gt"] = data; break;
		     case 'le': mongo[field]["$lte"] = data; break;
		     case 'ge': mongo[field]["$gte"] = data; break;
		     case 'nu': mongo[field]["$eq"] = " "; break;
		     case 'nn': mongo[field]["$ne"] = " "; break;
		     case 'bw': mongo[field]["$regex"] = "^"+data; break;
		     case 'ew': mongo[field]["$regex"] = data+"$"; break;
		     case 'cn': mongo[field]["$regex"] = data; break;
		     case 'bn': mongo[field]["$regex"] = "^(?!"+data+").*"; break;
		     case 'en': mongo[field]["$regex"] = ".*(?<!"+data+")$"; break;
		     case 'nc': mongo[field]["$regex"] = "^((?!"+data+").)*$"; break;
		}
	    }

	    if ( mongo[field].hasOwnProperty("$regex") ){
		    mongo[field]["$options"] = "i";
	    }
	    selection.push(mongo);
	}
	result[groupOp] = selection;
    }
    return result;
}

var buildMongoSort = function(gridRequest) {
    var sorter = {};
    var ord = 1;

    if( gridRequest.sidx && gridRequest.sidx.length > 0 ) {
	if( gridRequest.sord && gridRequest.sord == 'asc' )
	    sorter[gridRequest.sidx] = 1;
	else {
	    sorter[gridRequest.sidx] = -1;
	    ord = -1;
	}
    }
    /*If table was previously sorted we use that as a secondary sort*/
    if (gridRequest.hasOwnProperty("lastSortName[]") && gridRequest["lastSortName[]"].length >= 1){
	for (var i = gridRequest["lastSortName[]"].length - 1; i >=0 ; i--){
	    if (gridRequest["lastSortName[]"][i] !== gridRequest.sidx){
		if (gridRequest["lastSortOrder[]"][i] === "asc"){
		    sorter[gridRequest["lastSortName[]"][i]] = 1;
		}else{
		    sorter[gridRequest["lastSortName[]"][i]] = -1;
		}
	    }
	}
    }else {
	sorter["initialSort"] = ord;
    }

    return sorter;
}

var fetchtabledata = function (message, res) {

    // This code is full of nested callbacks.  We have to open the database, then 
    // in that callback we have to open the collection, then inside that callback we have to
    // process the count function, then in that callback we get the requested records, and inside 
    // that callback we send the records to the client.  Whew, that's a lot of callbacks.

    // We are in the OPEN callback
    try {
	mongoCacheDb.collection(message.collection, function (err, collection) {
	    // We are in the GET COLLECTION callback
	    var selection = buildMongoSelection(message);
	    var sorter = buildMongoSort(message);
	    var index = {};
	    var collist = {};
	    
	    if( message.collist ){
		collist = JSON.parse(message.collist);
	    }

	    collection.createIndex(sorter);
	    collection.find({$and:[{ "datalookupid": message.datalookupid },selection]}).count(function(err,totalcount){
		// We are in the COUNT results callback 
		collection.find({$and:[{ "datalookupid": message.datalookupid },selection]},collist).sort(sorter).skip((parseInt(message.page)-1)*parseInt(message.rows)).limit(parseInt(message.rows)).toArray(function (err, items) {
		    // We are in the GET RECORDS callback
		    var result = {};
		    result.records = parseInt(totalcount);
		    result.page = parseInt(message.page);
		    result.total = Math.ceil(parseInt(totalcount) / parseInt(message.rows));
		    var rowtmp = [];
		    if( items ) {
			for( var i=0; i < items.length; i++ ) {
			    rowtmp.push(items[i]);
			}
		    }
		    result["rows"] = rowtmp;
		    res.end(JSON.stringify(result));
		});
	    });
	});
    } catch (e) {
	console.log("ERROR: connecting to db");
	console.log(e);
	console.log(e.stack);
    }
};



var WebSocket = require('ws').WebSocket;
var WebSocketServer = require('ws').Server;
var net = require('net');
var hexy = require('hexy');
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var crypto = require('crypto');
var node_cryptojs = require('node-cryptojs-aes');
var CryptoJS = node_cryptojs.CryptoJS;
var JsonFormatter = node_cryptojs.JsonFormatter;
if (sslEnabled)
    var https = require('https');
else
    var https = require('http');

function processDataRequest(req, res) {
    var url = require('url');
    var url_parts = url.parse(req.url, true);
    var query = url_parts.query;
    if( url_parts.pathname.indexOf('/getGridData') != -1 ) {
	res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' });	
	fetchtabledata( query, res );
    }
    else if( url_parts.pathname.indexOf("/getResource/") != -1 ) {
	var fileId = url_parts.pathname.substring(url_parts.pathname.indexOf("/getResource/")+13);
	var gridStore = new GridStore(mongoCacheDb, fileId, fileId, "r",{'root':'resource'})
	    .open(function(err,gridStore){
		    if( gridStore ) {
			var stream = gridStore.stream(true);
			if ( gridStore.metadata.state < 8 ){			
			    res.writeHead(200,{'Content-Type': 'image', 'Cache-Control': 'max-age=31536000'});
			}else{
			    res.writeHead(200,{'Content-Type': 'text', 'Cache-Control': 'max-age=31536000'});
			}
			stream.pipe(res);
			gridStore.close(function(err, result){
			});
					
		    }
		});
    }
    else if( url_parts.pathname.indexOf("/getFile/") != -1 ) {
	/*If we pushed file into mongo to be displayed in the browser*/
	var fileId = url_parts.pathname.substring(url_parts.pathname.indexOf("/getFile/")+9);
	var gridStore = new GridStore(mongoCacheDb, fileId, fileId, "r",{'root':'file'})
	    .open(function(err,gridStore){
		    if( gridStore ) {
			var stream = gridStore.stream(true);
			var mime = require("mime");
			var mimeType = mime.lookup(fileId);
			res.writeHead(200,{'Content-Type': mimeType, 'Cache-Control': 'max-age=31536000'});
			stream.pipe(res);
			/*We give a 5 second delay to make sure the file has finished 
			**streaming to the client, then we remove the file from
			**the gridstore and close the connection.*/
			setTimeout(function(){
			    gridStore.unlink();
			    gridStore.close(function(err, result){
			    });
			},5000);
		    }else{
			/*If gridstore is not found, then we return a file not found
			**error*/
			res.writeHead(404,{'Content-Type': 'text/plain'});
			res.write('404 Not Found\n');
			res.end();
		    }
	    });
    }
}

// Configure the websocket
if (sslEnabled) {
    var options = {
        key: fs.readFileSync(sslPrivateKey),
        cert: fs.readFileSync(sslCertificate),
        ca: fs.readFileSync(sslCertAuthority)
    };

    var app = https.createServer(options, function (req, res) {
	    processDataRequest(req,res);
    }).listen(connectorPort);
}
else {
    var app = https.createServer(function (req, res) {
	    processDataRequest(req,res);
    }).listen(connectorPort);
}

// start a websocket server
var wss = new WebSocketServer({
    server: app
});

// globals
var clientnumber = 0;

// Websocket Connection Handler
wss.on('connection', function (ws) {
    ws.send2 = function (s) {
        var m = s;
        if (cryptoEnabled)
	    m = CryptoJS.AES.encrypt(s, "APPX", {
                format: JsonFormatter
            }).toString();
	try {
        return (this.send(m));
	}
	catch(ex) {
	    coonsole.log("send2() failed, exception="+ex);
	}
    };

    // Add connection meta data to this message for easier debugging
    logactivity("user connected...");

    //increment the client number to have a way to kill it's session's server loop
    var myid = clientnumber++;
    // Start a server loop specific to the client
    // The scope of variables in Javascript and Node allows this websocket connection ("ws") to be
    // mappped to the APPX socket ("client") created below
    var appxprocessor = new APPXProcessor(ws, myid);

    //passed login, so now we're safe to connect to the db
    //will we need this on reconnect and new session?
    //may want to add user/pass to mongodb to challenge
    appxprocessor.mongoconnector = new appxTableDataHandler();
    appxprocessor.mongoconnector.connecttodb();

    // Create a socket client to APPX
    var client_appx_socket = new net.Socket();
    appxprocessor.clientsocket = client_appx_socket;
    
    // Map a function to cleanup on websocket close event
    ws.on('close', function () {
        logactivity('ws client disconnected... #:' + myid);
        client_appx_socket.destroy();
        appxprocessor.end();
    });

    // Map a function to log on websocket error event
    ws.on('error', function (err) {
        logactivity('ws client error occurred' + err);
    });

    // Add a 'close' event handler for the client socket
    client_appx_socket.on('close', function () {
        logactivity('ALERT:  Connection closed');
    });

    // Map a function to handle data from the APPX server
    client_appx_socket.on('data', function (data) {
        // push data from the server onto the clients buffer(an array)
        // Here we could use a NodeJS Buffer object, but by using a Javascript Array, this code will work in browser
        // allowing for easier debugging
	if ( appxIsToJsonAnArray ) {
	    appxprocessor.rtndata = appxprocessor.rtndata.concat(data.toJSON(), new Array());
	}
	else {
	    arrayPush.apply(appxprocessor.rtndata,data.toJSON()['data']);
	}
        // if we have received enough bytes to satify the next parser request
        // run the parser before receiving any more data.
        if (appxprocessor.needbytes <= appxprocessor.rtndata.length) {
            var prcd;
            try {
                prcd = appxprocessor.appxprocessmsg();
            }
            catch (ex) {
                logactivity("appxprocessmsg() failed, ex=" + ex);
		console.log(ex.stack);
            }
        }
    });

    // Add an 'end' event handler for the client socket
    client_appx_socket.on('end', function () {
        logactivity('ALERT:  client_appx_socket disconnected');
	ws.close();
        appxprocessor.end();
    });

    // Map a function to log client error event
    client_appx_socket.on('error', function (err) {
        logactivity('ERROR:  client_appx_socket error \n\n' + err);
	ws.close();
        appxprocessor.end();
    });

    // Map a function to handle web client message events
    ws.on('message', function (messageCrypt) {
        var message = messageCrypt;
        if (cryptoEnabled) {
            message = CryptoJS.AES.decrypt(messageCrypt, "APPX", {
                format: JsonFormatter
            });
            message = CryptoJS.enc.Utf8.stringify(message);
        }

        // stub up a message object
        var ms = {};
        var g;

        try {
            ms = JSON.parse(message);
            g = true;
            logactivity("received good message:  " + message);
        }
        catch (appxerror) {
            logactivity("Message:  " + message);
            logactivity(appxerror);
            g = false;
	    console.log("Message: " + message);
	    console.log(appxerror.stack);
        }

        try {
            if (g) {
                switch (ms.cmd) {
                     case "openfile":
                         // may not use this
                         break;
                     case "appxlogin":
                         //Connect to Appx Connection Manager
                         client_appx_socket.connect(parseInt(ms.args[1]), ms.args[0], function () {
                             logactivity("CONNECTED TO " + ms.args[0] + ":" + ms.args[1]);
                         });
                         //created and send login to APPX
			 appxprocessor.uid = ab2str(ms.args[2]);
                         var tlogin = new Buffer(331);
                         tlogin.fill();
                         tlogin.write(ms.args[2]);
                         tlogin.write(ms.args[3], 21);
                         client_appx_socket.write(tlogin);
			 appxprocessor.cacheCollection = ms.args[0] + "_" + ms.args[1];
			 appxprocessor.hostCollection = ms.args[0] + "/" + ms.args[1];
                         break;
                     case "appxreconnect":
                         //Connect to Appx Connection Manager
                         client_appx_socket.connect(parseInt(ms.args[1]), ms.args[0], function () {
                             logactivity("CONNECTED TO " + ms.args[0] + ":" + ms.args[1]);
                         });
                         //created and send login to APPX
                         var treconnect = new Buffer(331);
                         treconnect.fill();
                         treconnect.write(ms.args[2]);
                         treconnect.write(ms.args[3], 21);
                         for (var rc = 0; rc < ms.args[4].length; rc++) {
                             treconnect[320 + rc] = ms.args[4].charCodeAt(rc);
                         }
                         client_appx_socket.write(treconnect);
                         break;
                     case "appxnewsession":
                         //Connect to Appx Connection Manager
                         client_appx_socket.connect(parseInt(ms.args[1]), ms.args[0], function () {
                             logactivity("CONNECTED TO " + ms.args[0] + ":" + ms.args[1]);
                         });
                         //created and send login to APPX
                         var tlogin = new Buffer(331);
                         tlogin.fill();
                         tlogin.write(ms.args[2]);
                         tlogin.write(ms.args[3], 21);
                         //add 32 bytes for host and remap
                         if (ms.args.length > 4) {
                             if (ms.args[4]) {
                                 tlogin.write(ms.args[4], 74);
                             }
                             if (ms.args[5]) {
                                 tlogin.write(ms.args[5], 77);
                             }
                             if (ms.args[6]) {
                                 tlogin.write(ms.args[6], 80);
                             }
                             if (ms.args[7]) {
                                 tlogin.write(ms.args[7], 90);
                             }
                         }
                         client_appx_socket.write(tlogin);
                         break;
                     case "appxmessage":
                         var b = new Buffer(ms.args);
                         logactivity("ARGS:  " + b);
                         logHexDump("CLI->APX", b, "Client message to Server...");
                         client_appx_socket.write(b);
                         break;
                     case "appxfileuploadmessage":
                         var b = new Buffer(ms.args);
                         logactivity("ARGS:  " + b);
                         logHexDump("CLI->APX", b, "Client message to Server...");
                         client_appx_socket.write(b);
                         var msrtn = new Message();
                         msrtn.data = ms.data;
                         msrtn.datatype = "object";
                         msrtn.hasdata = "yes";
                         msrtn.haserrors = "no";
                         msrtn.type = "APPXFILEUPLOADPROGRESS";
			 sendMessage(ws, msrtn);
                         break;
                     case "appxclipboard":
                         var msg = new Buffer(ms.args[0].length + 6);
                         msg.writeUInt32BE(ms.args[0].length, 0); // group
                         msg.writeUInt8(3, 4); // filler
                         msg.writeUInt8(1, 5); // ?senddata, 0 for now, 1 if data length is > amount server asked for
                         //the actual clipboard data
                         msg.write(ms.args[0], 6);
                         logactivity("ARGS:  " + ms.args + ", MSG: " + msg);
                         logHexDump("CLI->APX", msg, "Client message to Server...");
                         client_appx_socket.write(msg);
                         break;
                     case "appxtoken":
                         var msg = new Buffer(12);
                         appxprocessor.curtoken = new TokenStructure();
                         msg.writeUInt32BE(new DataView(new Uint8Array(ms.args.slice(0, 2)).buffer).getUint16(0), 0); // group
                         msg.writeUInt32BE(0, 4); // st pos
                         msg.writeUInt32BE(0, 8); // max pos
                         logactivity("ARGS: " + ms.args + ", MSG: " + msg);
                         logHexDump("CLI->APX", msg, "Client message to Server...");
                         var header = new Buffer(8);
                         header.writeUInt32BE(3, 0);
                         header.writeUInt8(79, 4);
                         header.fill(0, 5);
                         appxprocessor.curtokencount++;
                         client_appx_socket.write(header);
                         client_appx_socket.write(msg);
                         break;
                     case "appxresource":
                         var argsArray = ms.args.substring(1).split(",");
                         var arg0Array = argsArray[0].split(".");
                         var arg2Array = argsArray[2].split(".");



			 var currency = argsArray[1];
			 var cacheid2 = argsArray[0];
			 if( currency.length > 8 ) {
			     cacheid2 = argsArray[0].substring(0,argsArray[0].length-11)+"XXXXXXXX"+argsArray[0].substring(argsArray[0].length-3);
			 }
			 var fileId = appxprocessor.hostCollection + "/" + cacheid2+"."+currency;
			 var gridStore = new GridStore(mongoCacheDb, fileId, fileId, "r",{'root':'resource'})
			     .open(function(err,gridStore){
				 if( gridStore ) {
				     var res = new ResourceStructure();
				     res.ap =      gridStore.metadata.ap;
				     res.ver =     gridStore.metadata.ver;
				     res.cacheid = str4ab(argsArray[0].substring(7,15));
				     res.state =   gridStore.metadata.state;
				     res.ext =     gridStore.metadata.ext;
				     var path =    gridStore.metadata.url;
				     res.loctype = 1;
				     res.data = str4ab(gridStore.metadata.url);
				     res.len = res.data.length;
				     var ms = new Message();
				     ms.data = res;
				     ms.datatype = "object";
				     ms.hasdata = "yes";
				     ms.haserrors = "no";
				     ms.type = "APPXRESOURCE";
				     sendMessage(ws, ms);
				     
				     gridStore.close(function(err, result) {
				     });
				 }
				 else {
				     var msg = new Buffer(24);
				     msg.write(arg0Array[0].slice(0, 3), 0); // ap
				     msg.write(arg0Array[1].slice(0, 2), 3); // ver
				     msg.write(arg0Array[2].slice(0, 8), 5); // cacheid
				     msg.writeUInt8(parseInt("0x" + arg0Array[3].slice(0, 2)), 13); // state
				     msg.writeUInt16BE(parseInt("0x" + arg2Array[1].slice(0, 4)), 14); // id
				     msg.writeUInt32BE(parseInt("0x" + arg2Array[0].slice(0, 8)), 16); // ctx
				     msg.writeUInt8(parseInt("0x" + arg2Array[2].slice(0, 2)), 20); // type
				     msg.fill(0, 21); // filler
				     
				     var header = new Buffer(8);
				     header.writeUInt32BE(12, 0);
				     header.writeUInt8(83, 4);
				     header.fill(0, 5);
				     appxprocessor.curresourcelistOut[argsArray[0]] = argsArray[1];
				     appxprocessor.curresourcecountOut++;
				     client_appx_socket.write(header);
				     client_appx_socket.write(msg);
				 }
			     });
			 


                         break;
                     case "appxdate":
                         var msg = new Buffer(18);
                         appxprocessor.cursetfield = new SetFieldStructure();
                         msg.writeUInt8(ms.args[0], 0); // row
                         msg.writeUInt8(ms.args[1], 1); // col
                         msg.write(ms.args[2], 2); // date alpha16 string

                         var header = new Buffer(8);
                         header.writeUInt32BE(18, 0);
                         header.writeUInt8(84, 4);
                         header.fill(0, 5);
                         appxprocessor.cursetfieldcount++;
                         client_appx_socket.write(header);
                         client_appx_socket.write(msg);
                         break;
                         //TOD) - FIXME - REENGINEER show messages
                     case "appxsendshow":
                         var msg = new Buffer(18);
                         appxprocessor.cursetfield = new SetFieldStructure();
                         msg.writeUInt8(ms.args[0], 0); // row
                         msg.writeUInt8(ms.args[1], 1); // col
                         msg.write(ms.args[2], 2); // date alpha16 string

                         var header = new Buffer(8);
                         header.writeUInt32BE(18, 0);
                         header.writeUInt8(84, 4);
                         header.fill(0, 5);
                         appxprocessor.cursetfieldcount++;
                         client_appx_socket.write(header);
                         client_appx_socket.write(msg);
                         break;
		     case "updatelocal":
			 fs.readFile("localConnectorUpdate.zip", function (err, data) {
                             if (!err) {
                                 ms.data = data;
                                 ms.hasdata = "yes";
                                 ms.haserrors = "no";
                                 ms.type = "UPDATE";
				 sendMessage(ws, ms);
                             }
                             else {
                                 console.log(err);
                                 ms.hasdata = "no";
                                 ms.haserror = "yes";
                                 ms.error = err;
				 sendMessage(ws, ms);
                             }
                         });			
			 break;
                     case "appxinit":
                         // This appxinit message provides the opportunity to push down any assets needed on the client
                         // Send anything needed to start the session
                         var sendfile = function (fi) {
                             fs.readFile(fi, 'utf8', function (err, data) {
                                 if (!err) {
                                     ms.data = data;
                                     ms.hasdata = "yes";
                                     ms.haserrors = "no";
				     ms.serverConnectorVersionStr = serverConnectorVersionStr;
				     ms.serverConnectorVersionNum = serverConnectorVersionNum;
				     ms.mongoStatus = mongoStatus;
				     
                                     ms.type = (fi.toLowerCase().indexOf(".js") != -1 ? "SCRIPT" : "STYLE");
				 sendMessage(ws, ms);
                                 }
                                 else {
                                     console.log(err);
                                     ms.hasdata = "no";
				     ms.haserror = "yes";
				     ms.serverConnectorVersionStr = serverConnectorVersionStr;
				     ms.serverConnectorVersionNum = serverConnectorVersionNum;
                                     ms.error = err;
				 sendMessage(ws, ms);
                                 }
                             });
                         };

                         // Send client item handler library code
                         sendfile('appx-client-item.js');
                         // Send client key handler library code
                         sendfile('appx-client-keys.js');
                         // Send client localos handler library code
                         sendfile('appx-client-localos.js');
                         // Send main Javascript Library code to handle messages like Widgets, etc
                         sendfile('appx-client-main.js');
                         // Send client menu handler library code
                         sendfile('appx-client-menu.js');
                         // Send client resource handler library code
                         sendfile('appx-client-resource.js');
                         // Send client screen handler library code
                         sendfile('appx-client-screen.js');
                         // Send client session handler library code
                         sendfile('appx-client-session.js');
                         // Send client token handler library code
                         sendfile('appx-client-token.js');
                         // Send client token handler library code
                         sendfile('appx-client-setfield.js');
                         // Send client utility library code
                         sendfile('appx-client-util.js');
                         // Send client widget handler library code
                         sendfile('appx-client-widget.js');
                         // Send client options handler library code
                         sendfile('appx-client-options.js');
                         break;
		     case "ping":
			 break;
		     default:
                         logactivity('no message');
                }
            }
        }
        catch (appxerror2) {
            logactivity(appxerror2);
	    console.log(appxerror2.stack);
        }
    });
});

logactivity("Server Running...");

// Logging function, added tracelevel for verbose logging
// May need to make sure file is writable before logging if too much logging is happening
function logactivity(data) {
    try {
        var delimiter = "\r\n";
        if (appxdebug) {
            console.log(data);
        }
        if (appxlog) {
            fs.appendFile(logfile, delimiter + data, function (err) {
                if (err) {
                    console.log("Log File Error:  " + err);
                }
            });
        }
    }
    catch (e) {
        console.log(e);
	console.log(e.stack);
    }
}

function consoleLogHexDump(tag, data, desc) {
    var format = {
        format: "twos",
        prefix: "[" + tag + "] "
    }
    console.log("");
    console.log("[" + tag + "] " + desc);
    console.log("[" + tag + "] " + data.length + " bytes logged");
    console.log(hexy.hexy(data, format));
}

function logHexDump(tag, data, desc) {
    if (!appxdebug)
        return;
    var format = {
        format: "twos",
        prefix: "[" + tag + "] "
    }
    logactivity("");
    logactivity("[" + tag + "] " + desc);
    logactivity("[" + tag + "] " + data.length + " bytes logged");
    logactivity(hexy.hexy(data, format));
}

// APPXProcessor. Object
// Handle all server Messages
function APPXProcessor(ws, id) {
    // Create a reference to this instance of the APPXProcessor.
    var self = this;
    self.uid = null;
    self.pid = null;
    self.cacheCollection = null;
    self.hostCollection = null;
    self.pendingInserts = 0;        
    self.clientid = id;
    self.ws = ws;
    self.rtndata = [];
    self.loggedin = false;
    self.appxserverversion = null;
    self.init_optimization_flag = null;
    self.override = false;
    self.current_show = null;
    self.widgetCount = 0;
    self.widgets = []; 
    self.items = []; 
    self.rowHeightPx = 21;
    self.colWidthPx = 8;
    self.screenrows = 31;
    self.screencols = 128;
    self.fileData = [];
    self.msgCount =0;
    self.gridStore = null;
    self.needbytes = 4;
    self.curhandler = -1;
    self.curhandlerstep = 0;
    self.serverFeatureMask = 0;
    self.serverExtendedFeatureMask = 0;
    self.curresourceIn = null;
    self.curresourcelistOut = {};
    self.curresourcelistIn = {};
    self.curresourcecountIn = 0;
    self.curresourcecountOut = 0;
    self.cursetfield = null;
    self.cursetfieldcount = 0;
    self.curtokencount = 0;
    self.menu = null;
    self.cursetclip = null;
    self.current_msg = {
        "header": []
    };

    this.mongoconnector = {};
    // Ends a web client's server loop
    this.end = function () {
	this.mongoconnector.clearallappxtabledata(this.cacheCollection);
	this.mongoconnector.disconnectfromdb();
    };

    // APPX message handlers to send JSON packets to client rather than RAW socket data
    // Server Message Handler Functions
    // Processes server messages
    this.appxprocessmsg = function () {
        while ((this.rtndata.length > 0 && this.rtndata.length >= self.needbytes) || (self.needbytes == 0 && this.curhandler > 0)) {
            switch (this.curhandler) {
                 case -1:
                     { // Login needs to be done
                         this.appxloginhandler();
                         break;
                     }
                 case 0:
                     { // Message Block
                         this.appxheaderhandler();
                         break;
                     }
                 case 6:
                     { // Message Block
                         this.appxreconnecthandler();
                         break;
                     }
                 case 10:
                     { // INIT
                         this.appxinithandler();
                         break;
                     }
                 case 12:
                     { // SHOW
                         this.appxshowhandler();
                         break;
                     }
                 case 14:
                     { // FINISHED
                         this.appxfinishhandler();
                         break;
                     }
                 case 17:
                     { // ATTACH
                         this.appxattachhandler();
                         break;
                     }
                 case 19:
                     { // KEYMAP
                         this.appxkeymaphandler();
                         break;
                     }
                 case 21:
                     { // UPDATE SCREEN
                         this.appxscreenhandler();
                         break;
                     }
                 case 22:
                     { // UPDATE ATTRIBUTE
                         this.appxattributeshandler();
                         break;
                     }
                 case 25:
                     { // PING
                         this.appxpinghandler();
                         break;
                     }
                 case 26:
                     { // LOAD URL
                         this.appxloadurlhandler();
                         break;
                     }
                 case 27:
                     { // UPDATE EXTENDEDATTRIBUTE
                         this.appxextraattributeshandler();
                         break;
                     }
                 case 64:
                     { // CREATE OBJECT
                         //this.appxobjectshandler();
                         this.appxobjectcreatehandler();
                         break;
                     }
                 case 65:
                     { // INVOKE METHOD
                         //this.appxobjectshandler();
                         this.appxobjectinvokehandler();
                         break;
                     }
                 case 66:
                     { // DESTROY OBJECT
                         //this.appxobjectshandler(true);
                         this.appxobjectdestroyhandler();
                         break;
                     }
                 case 68:
                     { // FEATURE EXCHANGE
                         this.appxfeatureshandler();
                         break;
                     }
                 case 69:
                     { // SERVER PROCESS ID
                         this.appxpidhandler();
                         break;
                     }
                 case 71:
                     { // SEND FILE
                         this.appxsendfilehandler();
                         break;
                     }
                 case 72: //TAddField.type
                     { // ITEMS
                         this.appxitemshandler();
                         break;
                     }
                 case 73: //TAddField.type
                     { // ITEMS
                         this.appxreceivefilehandler();
                         break;
                     }
                 case 75:
                     { // WIDGETS
                         this.appxwidgetshandler();
                         break;
                     }
                 case 79:
                     { // TOKEN
                         this.appxtokenhandler();
                         break;
                     }
                 case 81:
                     { // MENUS
                         this.appxmenuhandler();
                         break;
                     }
                 case 83:
                     { // RESOURCE
                         this.appxresourcehandler();
                         break;
                     }
                 case 85:
                     { // SETFIELD
                         this.appxsetfieldhandler();
                         break;
                     }
                 case 87:
                     { // SET CLIPBOARD
                         this.appxsetclipboardhandler();
                         break;
                     }
                 case 88:
                     { // GET CLIPBOARD
                         this.appxgetclipboardhandler();
                         break;
                     }
                 case 89:
                     { // CONSTANTS EXCHANGE
                         this.appxconstantshandler();
                         break;
                     }
                 case 91:
                     { // GET MESSAGES
                         this.appxgetmessageshandler();
                         break;
                     }
                 case 93:
                     { // PROC STACK
                         this.appxprocstackhandler();
                         break;
                     }
                 case 94:
                     { // EXTENDED FEATURE EXCHANGE
                         this.appxextendedfeatureshandler();
                         break;
                     }
                 default:
                     {
                         //exit;
                         break;
                     }
            }
        }
        logactivity("appxprocessmsg exiting, needbytes=" + this.needbytes + "gotbytes=" + this.rtndata.length);
    };

    var T_BOOLEAN = 1; // 1 byte
    var T_BYTE = 2; //1 byte
    var T_CHAR = 3; //1 byte
    var T_DOUBLE = 4; //8 bytes
    var T_FLOAT = 5; //4 bytes
    var T_INT = 6; //4 bytes
    var T_LONG = 7; //4 bytes
    var T_SHORT = 8; //2 bytes
    var T_UNSIGNED_BYTE = 9; //1 byte
    var T_UNSIGNED_SHORT = 10; //2 bytes
    var T_STRING = 11;
    var T_DATE = 12; 

    this.dataType = null;
    this.data = function () {
        var ret = this.rtndata.slice(0, this.needbytes); 
        if (this.dataType && ret.length > 0) {
            var dv = new DataView(new Uint8Array(ret).buffer);
            switch (this.dataType) {
                 case T_BYTE:
                     ret = dv.getInt8(0);
                     break;
                 case T_UNSIGNED_BYTE:
                 case T_BOOLEAN:
                 case T_CHAR:
                     ret = dv.getUint8(0);
                     break;
                 case T_SHORT:
                     ret = dv.getInt16(0);
                     break;
                 case T_UNSIGNED_SHORT:
                     ret = dv.getUint16(0);
                     break;
                 case T_INT:
                     ret = dv.getInt32(0); 
                     break;
                 case T_FLOAT:
                     ret = dv.getFloat32(0);
                     break;
                 case T_DOUBLE:
                 case T_LONG: 
                     ret = dv.getFloat64(0);
                     break;
                 default:
                     ret = ab2str(ret).trim();
            }
            this.dataType = null;
        }
        this.rtndata = this.rtndata.slice(this.needbytes); 
        return ret;
    };
    this.done = function () {
        this.curhandler = 0;
        this.curhandlerstep = 0;
        this.needbytes = 8;
    };

    this.read = function (len, type) {
        this.dataType = (type ? type : T_STRING);
        this.curhandlerstep++;
        this.needbytes = len;
    };
    this.readByte = function () {
        this.read(1, T_BYTE);
    };
    this.readShort = function () {
        this.read(2, T_SHORT);
    };
    this.readInt = function () {
        this.read(4, T_INT);
    };
    this.send = function (type, data) {
        var ms = new Message();
        ms.type = type;
        if (data) {
            ms.data = data;
            ms.datatype = "object";
            ms.hasdata = true;
        }
        ms.haserrors = false;
        ms.errormsg = "";
        sendMessage(ws, ms);
        this.done();
    };

    this.step = function () {
        return this.curhandlerstep;
    };
    this.stepTo = function (step) {
        this.curhandlerstep = step;
        this.needbytes = 0;
    };

    this.hdr = null;
    this.arg = null;
    this.appxobjecthandler = function (type) {
        switch (this.step()) {
             case 0:
                 this.hdr = {}; 
                 this.hdr.argBlocks = [];
                 this.readShort();
                 break;
             case 1:
                 this.hdr.protoLen = this.data();
                 this.readByte();
                 break;
             case 2:
                 this.hdr.argCount = this.data();
                 this.readByte();
                 break;
             case 3:
                 this.hdr.handle = this.data();
                 this.read(this.hdr.protoLen);
                 break;
             case 4:
                 this.hdr.proto = this.data();
                 if (this.hdr.argCount == 0) this.stepTo(9);
                 else this.read(0);
                 break;
             case 5:
                 this.arg = {}; 
                 this.readInt();
                 break;
             case 6:
                 //tmnetsrv actually only supports ELEM_ALP_CONTIG (string) atm
                 this.arg.dataType = this.data();
                 this.readInt();
                 break;
             case 7:
                 this.arg.dataLength = this.data();
                 this.read(this.arg.dataLength, this.arg.dataType);
                 break;
             case 8:
                 this.arg.dataObject = this.data();
                 this.hdr.argBlocks.push(this.arg);
                 if (--this.hdr.argCount > 0) this.stepTo(5);
                 else this.read(0); //stepTo(9)
                 break;
             case 9:
                 this.send("APPX" + type + "OBJECT", this.hdr);
                 break;
        }
    };
    this.appxobjectcreatehandler = function () {
        this.appxobjecthandler("CREATE");
    }; //64
    this.appxobjectinvokehandler = function () {
        this.appxobjecthandler("INVOKE");
    }; //65
    this.appxobjectdestroyhandler = function () {
        this.appxobjecthandler("DESTROY");
    }; //66

    // Login Handler
    this.appxloginhandler = function () {
        logactivity("***** SOCKET_LOGIN *****");

        switch (this.curhandlerstep) {
             case 0:
                 var loginresponse = this.rtndata.slice(0, 2);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);

                 if (loginresponse[1] == 1) {

                     logactivity("logged in...");
                     this.loggedin = true;
                     this.appxserverversion = this.rtndata.slice(0, 2);
                     this.rtndata = this.rtndata.slice(2, this.rtndata.length);

                     var ms = new Message();
                     ms.type = "APPXLOGIN";
                     ms.data = "The user logged in successfully!";
                     ms.datatype = "string";
                     ms.hasdata = true;
                     ms.transresult = "SUCCESS";
                     ms.haserrors = false;
                     ms.errormsg = "";
		     sendMessage(ws, ms);

                     this.curhandler = 0;
                     this.curhandlerstep = 0;
                     this.needbytes = 8;
                 }
                 else {
                     this.curhandlerstep = 1;
                     this.needbytes = 4;
                 }

                 break;
             case 1:

                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getInt32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 this.curhandlerstep = 2;

                 break;
             case 2:

                 var msg = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 var ms = new Message();
                 ms.type = "APPXLOGIN";
                 ms.data = ab2str(msg);
                 ms.datatype = "string";
                 ms.hasdata = true;
                 ms.transresult = "FAIL";
                 ms.haserrors = true;
                 ms.errormsg = "Login failed";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.curhandlerstep = 0;
                 this.needbytes = 8;

                 break;
             default:
                 break;
        }

    };

    // Reconnect Handler
    this.appxreconnecthandler = function () {
        logactivity("***** SOCKET_RECONNECT *****");
        switch (this.curhandlerstep) {
             case 0:
                 var bytes = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 this.recondatalength = new DataView(new Uint8Array(bytes).buffer).getUint32(0);
                 this.curhandlerstep = 1;
                 this.needbytes = this.recondatalength;
                 break;
             case 1:
                 var bytes = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 //send the row cols to the client
                 var ms = new Message();
                 ms.data = bytes;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXRECONNECT";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.curhandlerstep = 0;
                 this.needbytes = 8;
                 break;
        }

    };

    this.appxfinishhandler = function () {

        this.loggedin = false;
        this.curhandler = -1;
        this.curhandlerstep = 0;
        this.needbytes = 0;
        this.rtndata = this.rtndata.slice();
        var ms = new Message();
        ms.type = "APPXFINISH";
	sendMessage(ws, ms);

    };

    // Header Handler
    this.appxheaderhandler = function () {
        logactivity("***** SOCKET_HEADER *****");

        self.current_msg.header = this.rtndata.slice(0, 8);
        this.rtndata = this.rtndata.slice(8, this.rtndata.length);
        var constants_bytes = self.current_msg.header.slice(0, 4);
        var constants_length = new DataView(new Uint8Array(constants_bytes).buffer).getUint32(0);

        self.needbytes = constants_length;
        self.curhandler = self.current_msg.header[4];
        self.curhandlerstep = 0;

        logactivity("header processed, length=" + self.needbytes + ", command=" + self.curhandler);
    };

    // Init Handler
    this.appxinithandler = function (data) {
        logactivity("***** SOCKET_INIT *****, current step=" + this.curhandlerstep);
        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = 1;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.init_optimization_flag = this.rtndata.slice(0, 1);
                 this.rtndata = this.rtndata.slice(1, this.rtndata.length);

		 var ms = new Message();
                 ms.data = data;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXINIT";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // Features Handler
    this.appxfeatureshandler = function (data) {
        logactivity("***** SOCKET_FEATURE_EXCHANGE *****");
        var bytes = this.rtndata.slice(0, 4);
        this.rtndata = this.rtndata.slice(4, this.rtndata.length);

        this.serverFeatureMask = new DataView(new Uint8Array(bytes).buffer).getUint32(0);
        var ms = new Message();
        ms.data = data;
        ms.hasdata = "yes";
        ms.haserrors = "no";
        ms.type = "APPXFEATURES";
	
	sendMessage(ws, ms);

        logactivity("server feature mask=" + this.serverFeatureMask);

        this.curhandler = 0;
        this.needbytes = 8;
        this.curhandlerstep = 0;
    };

    // Extended Features Handler //94
    this.appxextendedfeatureshandler = function (data) {
        logactivity("***** SOCKET_EXTENDED_FEATURE_EXCHANGE *****");
        var bytes = this.rtndata.slice(0, 4);
        this.rtndata = this.rtndata.slice(4, this.rtndata.length);

        this.serverExtendedFeatureMask = new DataView(new Uint8Array(bytes).buffer).getUint32(0);

        var ms = new Message();
        ms.data = data;
        ms.hasdata = "yes";
        ms.haserrors = "no";
        ms.type = "APPXEXTENDEDFEATURES";
	sendMessage(ws, ms);

        logactivity("server extended feature mask=" + this.serverExtendedFeatureMask);

        this.curhandler = 0;
        this.needbytes = 8;
        this.curhandlerstep = 0;
    };

    // Attach Handler
    this.appxattachhandler = function (data) {
        logactivity("***** SOCKET_ATTACH *****");
	var ms = new Message();
        ms.data = data;
        ms.hasdata = "yes";
        ms.haserrors = "no";
        ms.type = "APPXATTACH";
	sendMessage(ws, ms);

        this.curhandler = 0;
        this.needbytes = 8;
        this.curhandlerstep = 0;
    };

    // KeyMap Handler
    this.appxkeymaphandler = function (data) {
        logactivity("***** SOCKET_KEYMAP *****, step=" + this.curhandlerstep);

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = 10;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.mykeymapkey = this.rtndata.slice(0, 10);
                 this.rtndata = this.rtndata.slice(10, this.rtndata.length);
                 this.needbytes = 2;
                 this.curhandlerstep = 2;
                 break;
             case 2:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);
                 this.curhandlerstep = 3;
                 break;
             case 3:
                 if (this.needbytes > 0) {
                     self.mykeymapdata = rtndata.slice(0, this.needbytes);
                     this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 }
                 var ms = new Message();
                 ms.data = data;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXKEYMAP";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // PID Handler
    this.appxpidhandler = function () {
        logactivity("***** SOCKET_SERVER_PID *****, step=" + this.curhandlerstep);

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);

                 this.curhandlerstep = 1;
                 break;
             case 1:
                 var bdlen = 0;
                 var mypid = 0;
                 var mypidbytes = [];

                 mypidbytes = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 mypid = mypidbytes;

                 logactivity("my process id=" + mypid);

		 self.pid = ab2str(mypid);
		 self.cacheCollection = self.uid + "_" + self.pid;

                 var ms = new Message();
                 ms.data = mypid;
                 ms.datatype = "object";
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXPID";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // SEND FILE Handler
    this.appxsendfilehandler = function () {
        logactivity("got a file message...");
        switch (this.curhandlerstep) {
             case 0: // set up to read an item block from the server
                 this.currfile = new FileStructure();
                 this.needbytes = 4;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.currfile.datalength = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 this.needbytes = 4;
                 this.curhandlerstep = 2;
                 break;
             case 2:
                 this.currfile.filenamelength = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 this.needbytes = this.currfile.filenamelength;
                 this.curhandlerstep = 3;
                 break;
             case 3:
                 this.currfile.filename = ab2str(this.rtndata.slice(0, this.needbytes));
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
		 
                 this.needbytes = 4;
                 this.curhandlerstep = 4;

		 /*If we want to open file in the browser then we create a gridstore
		 **and mimic client response to the server so it will send file data*/
		 if (this.currfile.filename.indexOf("$(pushAndOpen)") > -1){
		     var fileId = this.currfile.filename.replace("$(", "").replace(")","");
		     var self = this;
		     if (this.gridStore === null){
			 this.gridStore = new GridStore(
			     mongoCacheDb, 
			     fileId, 
			     fileId, 
			     "w", 
			     {
				 'root':'file', 
				 'metadata': { 
				     'url':      "/getFile/"+fileId
				 }
			     }
			 );
		     }
		     this.gridStore.open(function(err, gridStore) {
			 /*Send response to server requesting rest of file*/
			 var b = new Buffer([3,1]);
			 self.clientsocket.write(b);
		     });
		 }else{
                     //send the file name on to the client so it can send a status
		     //structure whether or not to send the rest of the file.
                     logactivity("sending filename to client:" + this.currfile.filename);
		     
		     var ms = new Message();
                     ms.data = this.currfile;
                     ms.datatype = "object";
                     ms.hasdata = "yes";
                     ms.haserrors = "no";
                     ms.messagepart = 3; // 3 = filename
                     ms.type = "APPXSENDFILE";
		     sendMessage(ws, ms);
		 }
                 break;
	     case 4:
                 this.currfile.currdatachunklength = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
		 
                 if (this.currfile.currdatachunklength > 0) {
		     this.needbytes = this.currfile.currdatachunklength;
		     this.curhandlerstep = 5;
                 }
                 else {
		     /*If we want to open file in the browser, after we have received
		     **the full file, we close the gridstore, send message to client
		     **to load URL, and mimic clients response to server so APPX
		     **will continue to run*/
		     if (this.currfile.filename.indexOf("$(pushAndOpen)") > -1){
			 var self = this;
			 this.gridStore.close(function(err, result) {
			     /*Send loadurl message to client*/
			     var ms = new Message();
			     ms.data = self.currfile.filename;
			     ms.datatype = "URL";
			     ms.hasdata = "yes";
			     ms.haserrors = "no";
			     ms.type = "APPXLOADURL";
			     sendMessage(ws, ms);

			     /*Send response to server indicating file receipt*/
			     var nameAB = str4ab(self.currfile.filename);
			     var b = new Buffer([0,0,0,nameAB.length]);
			     self.clientsocket.write(b);
			     b = new Buffer(nameAB);
			     self.clientsocket.write(b);
			     b = new Buffer([3,1]);
			     self.clientsocket.write(b);
			     
			     self.gridStore = null;
			 });

			 
			 
		     }else{
		     
			 // send file EOF
			 var ms = new Message();
			 ms.data = this.currfile.currdatachunklength;
			 ms.datatype = "EOF";
			 ms.hasdata = "yes";
			 ms.msgCount = ++this.msgCount;
			 ms.haserrors = "no";
			 ms.messagepart = -1;
			 ms.type = "APPXSENDFILE";
			 sendMessage(ws, ms);
		     }
                     //exit this handler, reset to header handler
                     this.curhandler = 0;
                     this.needbytes = 8;
                     this.curhandlerstep = 0;
                 }

                 break;

             case 5:
                 this.currfile.currdatachunk = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
		 if (this.currfile.filename.indexOf("$(pushAndOpen)") > -1){
		     /*If we want to open file in the browser, keep adding chunks
		     **into the gridstore until file is finished*/

		     var dataBuffer = new Buffer(this.currfile.currdatachunk);
		     this.gridStore.chunkSize = 2048;
		     this.gridStore.write(dataBuffer, function(err,gridStore){
			 
		     });

		 }else{

		     this.fileData =  base64Array(this.currfile.currdatachunk);

                     // send file data chunk

                     var ms = new Message();
		     ms.data = this.fileData;
                     ms.datatype = "array";
                     ms.hasdata = "yes";
		     ms.msgCount = ++this.msgCount;
		     ms.haserrors = "no";
                     ms.messagepart = 5; // 5 = data chunk
                     ms.type = "APPXSENDFILE";
		     sendMessage(ws, ms);
		 }
		 // always go back to step 4 which handles EOF block
		 
                 this.needbytes = 4;
                 this.curhandlerstep = 4;

                 break;
        }
    };

    // LOAD URL Handler
    this.appxloadurlhandler = function () {
        logactivity("***** SOCKET_LOAD_URL *****, step=" + this.curhandlerstep);
        switch (this.curhandlerstep) {
             case 0: // set up to read an item block from the server
                 this.needbytes = 2;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);

                 this.curhandlerstep = 2;
                 break;
             case 2:
                 var bdlen = 0;
                 var myurlbytes = [];

                 myurlbytes = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 logactivity("my url=" + ab2str(myurlbytes));

		 var ms = new Message();
                 ms.data = ab2str(myurlbytes).trim();
                 ms.datatype = "object";
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXLOADURL";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // MENUS Handler
    this.appxmenuhandler = function () {

        logactivity("***** SOCKET_RESOURCE *****, step=" + this.curhandlerstep);

        var done = false;
        var menuItemDone = false;

        var nb = this.needbytes;
        switch (this.curhandlerstep) {

             case 0: // set up to read an menu headeer block from the server
                 this.menu = new Menu();
                 this.curhandlerstep++;
                 this.needbytes = 6;
                 break;

             case 1:
                 this.menu.headerstructure = this.rtndata.slice(0, 6);
                 //one byte can use rthdata.shift, but it might get confusing
                 this.menu.type = this.menu.headerstructure.slice(0, 1);
                 this.menu.itemcount = new DataView(new Uint8Array(this.menu.headerstructure.slice(2, 4)).buffer).getUint16(0);
                 this.menu.headerdatalength = new DataView(new Uint8Array(this.menu.headerstructure.slice(4, 6)).buffer).getUint16(0);

                 if (this.menu.headerdatalength > 0) {
                     this.curhandlerstep++;
                     this.needbytes = this.menu.headerdatalength;
                 }
                 else { // if menu datalength is 0 skip a step
                     this.curhandlerstep += 2;
                     this.needbytes = 0;
                 }
                 break;

             case 2: //fetch header
                 this.menu.headerdata = ab2str( this.rtndata.slice(0, this.needbytes) );
                 this.curhandlerstep++;
                 this.needbytes = 0;
                 break;

             case 3: //are there any menu items?
                 if (this.menu.itemcount > 0) {
                     this.curhandlerstep++;
                     this.needbytes = 4; //menu struct = 4 bytes
                 }
                 else {
                     done = true;
                     menuItemDone = true;
                 }
                 break;

             case 4: //get next menu item (MMnu)
                 this.curmenuitem = new MenuItem();
                 this.curmenuitem.structure = this.rtndata.slice(0, 4);
                 this.curhandlerstep++;
                 this.needbytes = new DataView(new Uint8Array(this.curmenuitem.structure.slice(2, 4)).buffer).getUint16(0);
                 break;

             case 5: //process menu item
                 this.curmenuitem.data = ab2str( this.rtndata.slice(0, this.needbytes) );
                 this.curhandlerstep++;
                 this.needbytes = 4;
                 break;

             case 6: // trim off and process extra data length
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getInt32(0);
                 if (this.needbytes > 0) {
                     this.curhandlerstep++;
                 }
                 else {
                     if (this.needbytes < 0) {
                         this.curmenuitem.extrareuse = true;
                     }
                     menuItemDone = true;
                 }
                 break;
             case 7: // trim off and process extra data
                 this.curmenuitem.extradata = this.rtndata.slice(0, this.needbytes);
                 menuItemDone = true;
                 break;
        }
        var hexdata = this.rtndata.slice(0, nb);
        logHexDump("ITEMDATA", hexdata, "item data");
        if (this.curhandlerstep > 1) this.rtndata = this.rtndata.slice(nb);

        if (menuItemDone) {
            this.menu.items.push(this.curmenuitem);
            if (this.menu.items.length < this.menu.itemcount) {
                this.curhandlerstep = 4;
                this.needbytes = 4;
            }
            else {
		var ms = new Message();
                ms.data = this.menu;
                ms.datatype = "array";
                ms.datacount = this.menu.items.length;
                ms.hasdata = "yes";
                ms.haserrors = "no";
                ms.type = "APPXMENU";
		sendMessage(ws, ms);
                done = true;
            }
        }

        if (done) {
            // setup to read next message header
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
            logHexDump("MENUDATA", "curmenu", "Resource Data Received...");
        }
    };

    // TOKEN Handler
    this.appxtokenhandler = function () {

        logactivity("***** SOCKET_TOKEN *****, step=" + this.curhandlerstep);

        var done = false;

        switch (this.curhandlerstep) {
             case 0:
                 this.curtokencount--;
                 var rawdata = this.rtndata.slice(0, 12);
                 this.rtndata = this.rtndata.slice(12, this.rtndata.length);

                 logHexDump("resHeader", rawdata, "Resource header...");
                 this.curtoken.grp = rawdata.slice(0, 4);
                 this.curtoken.start = rawdata.slice(4, 8);
                 this.curtoken.max = rawdata.slice(8, 12);
                 this.curtoken.items = [];

                 this.needbytes = 8;
                 this.curhandlerstep = 1;

                 break;

             case 1:
                 this.curtoken.totalsize = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint8(0);
                 this.curtoken.len = new DataView(new Uint8Array(this.rtndata.slice(4, 8)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(8, this.rtndata.length);

                 if (this.curtoken.len > 0) {
                     this.needbytes = 8;
                     this.curhandlerstep = 2;
                 }
                 else {
                     done = true;
                 }

                 break;
             case 2:
                 this.curtoken.data = [];

                 this.curtoken.data = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 var ti = new TokenItem();
                 ti.token_id = new DataView(new Uint8Array(this.curtoken.data.slice(0, 4)).buffer).getUint32(0);
                 ti.token_id_group = new DataView(new Uint8Array(this.curtoken.data.slice(4, 6)).buffer).getUint16(0);
                 ti.token_id_val_len = new DataView(new Uint8Array(this.curtoken.data.slice(6, 8)).buffer).getUint16(0);
                 if (ti.token_id_val_len != 0) {
                     this.needbytes = ti.token_id_val_len;
                     this.curhandlerstep = 3;
                 }
                 else {
                     if (this.curtoken.items.length = this.curtoken.len) {
                         done = true;
                     }
                     else {
                         this.needbytes = 8;
                         this.curhandlerstep = 2;
                     }
                 }
                 this.curtoken.items.push(ti);
                 break;
             case 3:
                 var ti = this.curtoken.items[this.curtoken.items.length - 1];

                 ti.data = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 if (this.curtoken.items.length == this.curtoken.len) {
                     done = true;
                 }
                 else {
                     this.needbytes = 8;
                     this.curhandlerstep = 2;
                 }
                 break;
        }

        if (done) {
            logHexDump("TOKENDATA", this.curtoken.data, "Resource Data Received...");

            var ms = new Message();
            ms.data = this.curtoken;
            ms.datatype = "object";
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXTOKEN";
	    sendMessage(ws, ms);

            logactivity("curtokencount=" + this.curtokencount);
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // RESOURCE Handler
    this.appxresourcehandler = function () {
        logactivity("***** SOCKET_RESOURCE *****, step=" + this.curhandlerstep);

        var done = false;

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = 20;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.curresourcecountIn--;
                 var rawdata = this.rtndata.slice(0, 20);
                 this.rtndata = this.rtndata.slice(20, this.rtndata.length);
                 logHexDump("resHeader", rawdata, "Resource header...");
                 if (this.curresourceIn == null)
                     this.curresourceIn = new ResourceStructure();
                 this.curresourceIn.ap = rawdata.slice(0, 3);
                 this.curresourceIn.ver = rawdata.slice(3, 5);
                 this.curresourceIn.cacheid = rawdata.slice(5, 13);
                 this.curresourceIn.state = new DataView(new Uint8Array(rawdata.slice(13, 14)).buffer).getUint8(0);
		 this.curresourceIn.stateHex = new DataView(new Uint8Array(rawdata.slice(13, 14)).buffer).getUint8(0).toString(16).toUpperCase();
                 this.curresourceIn.ext = new DataView(new Uint8Array(rawdata.slice(15, 16)).buffer).getUint8(0);
                 this.curresourceIn.loctype = new DataView(new Uint8Array(rawdata.slice(14, 15)).buffer).getUint8(0);
                 this.curresourceIn.len = new DataView(new Uint8Array(rawdata.slice(16, 20)).buffer).getUint32(0);
                 this.curresourceIn.data = [];

                 if (this.curresourceIn.len > 0) {
                     this.needbytes = this.curresourceIn.len;
                     this.curhandlerstep = 2;
                 }
                 else {
                     done = true;
                 }

                 break;
             case 2:
                 this.curresourceIn.data = [];
                 this.curresourceIn.data = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 done = true;
                 break;
        }

        if (done) {
            logHexDump("RESDATA", this.curresourceIn.data, "Resource Data Received...");
	    if( this.curresourceIn.loctype == 1) {
		var ms = new Message();
		ms.data = this.curresourceIn;
		ms.datatype = "object";
		ms.hasdata = "yes";
		ms.haserrors = "no";
		ms.type = "APPXRESOURCE";
		sendMessage(ws, ms);
	    }
	    else {
		var cacheid =ab2str(this.curresourceIn.ap) + "." + 
		ab2str(this.curresourceIn.ver) + "." +
		ab2str(this.curresourceIn.cacheid) + "." +
		(this.curresourceIn.state < 16 ? "0" : "") + 
		this.curresourceIn.stateHex;
		var currency = this.curresourcelistOut[cacheid];
                var cacheid2 = cacheid;
                if( currency.length > 8 ) {
		    cacheid2 = cacheid.substring(0,cacheid.length-11)+"XXXXXXXX"+cacheid.substring(cacheid.length-3);
		}
		var fileId = this.hostCollection + "/" + cacheid2+"."+currency;
		if( currency ) {
		    delete this.curresourcelistOut[cacheid];
		    this.curresourcecountOut--;
		}
		var gridStore = new GridStore(
		    mongoCacheDb, 
		    fileId, 
		    fileId, 
		    "w", 
                    {
                        'root':'resource', 
			'metadata': { 
			    'currency': currency,
			    'ap':       this.curresourceIn.ap,
			    'ver':      this.curresourceIn.ver,
			    'cacheid':  this.curresourceIn.cacheid,
			    'state':    this.curresourceIn.state,
			    'ext':      this.curresourceIn.ext,
			    'loctype':  this.curresourceIn.loctype,
			    'url':      "/getResource/"+fileId
			}
		    }
		);
		var proc = this;
		var dTemp;
		if (this.curresourceIn.state == 10){
		    dTemp = ab2str(this.curresourceIn.data);
		    dTemp = modifyCkeditor(dTemp);
		}else{
		    dTemp = this.curresourceIn.data;
		}
		if (dTemp.length === 0){
		    dTemp[0] = 0; 
		}
		var dataBuffer = new Buffer(dTemp);
		gridStore.chunkSize = 1024 * 256;
		gridStore.open(function(err, gridStore) {
			gridStore.write(dataBuffer, function(err,gridStore){
				var res = new ResourceStructure();
				res.ap =      gridStore.metadata.ap;
				res.ver =     gridStore.metadata.ver;
				res.cacheid = gridStore.metadata.cacheid;
				res.state =   gridStore.metadata.state;
				res.ext =     gridStore.metadata.ext;
				var path =    gridStore.metadata.url;
				res.loctype = 1;
				res.data = str4ab(gridStore.metadata.url);
				res.len = res.data.length;
				var ms = new Message();
				ms.data = res;
				ms.datatype = "object";
				ms.hasdata = "yes";
				ms.haserrors = "no";
				ms.type = "APPXRESOURCE";
				sendMessage(ws, ms);
				
				gridStore.close(function(err, result) {

				});
			    });
		});
	    }

	    logactivity("curresourcecount=" + this.curresourcecountIn);
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
	
    };

    // SET CLIPBOARD Handler - 87
    this.appxsetclipboardhandler = function () {

        logactivity("***** SOCKET_SETCLIPBOARD *****, step=" + this.curhandlerstep);

        var done = false;

        switch (this.curhandlerstep) {
             case 0:

                 this.cursetclip = {};

                 this.needbytes = 4;
                 this.curhandlerstep = 1;

                 break;

             case 1:

                 //rtndata was empty once, so putting in a check
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);

                 this.cursetclip.datatype = new DataView(new Uint8Array(rawdata.slice(0, 4)).buffer).getUint32(0);

                 this.needbytes = 4;
                 this.curhandlerstep = 2;
                 break;


             case 2:

                 //rtndata was empty once, so putting in a check
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);

                 this.cursetclip.datalength = new DataView(new Uint8Array(rawdata.slice(0, 4)).buffer).getUint32(0);

                 this.needbytes = this.cursetclip.datalength;
                 this.curhandlerstep = 3;
                 break;

             case 3:


                 this.cursetclip.data = ab2str(this.rtndata.slice(0, this.needbytes));
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 done = true;

                 break;
        }

        if (done) {
            logHexDump("SETCLIPBOARDDATA", this.cursetclip.data, "GetClipboard Data Message Received...");

            var ms = new Message();
            ms.data = this.cursetclip;
            ms.datatype = "string";
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXSETCLIPBOARD";
	    sendMessage(ws, ms);

            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // GET CLIPBOARD Handler - 88
    this.appxgetclipboardhandler = function () {
        logactivity("***** SOCKET_GETCLIPBOARD *****, step=" + this.curhandlerstep);

        var done = false;

        switch (this.curhandlerstep) {
             case 0:

                 this.needbytes = 9;
                 this.curhandlerstep = 1;

                 break;

             case 1:
                 var rawdata = this.rtndata.slice(0, 9);
                 this.rtndata = this.rtndata.slice(9, this.rtndata.length);

                 this.clipboardstruct = {};
                 this.clipboardstruct.datatype = new DataView(new Uint8Array(rawdata.slice(0, 4)).buffer).getUint32(0);
                 this.clipboardstruct.datalength = new DataView(new Uint8Array(rawdata.slice(4, 8)).buffer).getUint32(0);
                 this.clipboardstruct.senddata = rawdata.slice(8, 1);

                 done = true;
                 break;
        }

        if (done) {
            logHexDump("GETCLIPBOARDDATA", "", "GetClipboard Data Message Received...");

            var ms = new Message();
            ms.data = this.clipboardstruct;
            ms.datatype = "string";
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXGETCLIPBOARD";
	    sendMessage(ws, ms);

            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // CONSTANTS EXCHANGE Handler - 89
    this.appxconstantshandler = function () {
        logactivity("***** SOCKET_CONSTANTS EXCHANGE *****, step=" + this.curhandlerstep);

        var done = false;
	
        switch (this.curhandlerstep) {
             case 0: // get the first header
                 this.constants = {};
                 this.needbytes = 4;
                 this.curhandlerstep = 1;
		 this.constantcount = 0;
                 break;

             case 1: // process header
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 this.constantcount = new DataView(new Uint8Array(rawdata).buffer).getUint32(0);
		 if( this.constantcount == 0 ) {
		     done = true;
		 }
		 else {
		     this.needbytes = 4;
		     this.curhandlerstep = 2;
		 }
                 break;
             case 2: // process keyword length
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
		 this.needbytes = new DataView(new Uint8Array(rawdata).buffer).getUint32(0);
		 this.curhandlerstep = 3;
                 break;
             case 3: // process keyword
                 var rawdata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
		 this.constantkeyword = ab2str(rawdata);
		 this.needbytes = 4;
		 this.curhandlerstep = 4;
                 break;
             case 4: // process value length
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
		 this.needbytes = new DataView(new Uint8Array(rawdata).buffer).getUint32(0);
		 this.curhandlerstep = 5;
                 break;
             case 5: // process value
                 var rawdata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
		 this.constantvalue = ab2str(rawdata);
		 this.constants[this.constantkeyword] = this.constantvalue;
		 this.constantcount--;
		 if( this.constantcount > 0 ) {
		     this.needbytes = 4;
		     this.curhandlerstep = 2;
		 }
		 else {
		     done = true;
		 }
                 break;
        }

        if (done) {
	    var ms = new Message();
            ms.data = this.constants; //[];
            ms.datatype = "object";
            ms.datacount = 0;
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXCNSTS";
	    sendMessage(ws, ms);

            // setup to read next message header
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // GET MESSAGES Handler - 91
    this.appxgetmessageshandler = function () {
        logactivity("***** SOCKET_GETMESSAGES *****, step=" + this.curhandlerstep);

        var done = false;
        var allDone = false;

        switch (this.curhandlerstep) {
             case 0: // get the first header
                 this.messages = [];
                 this.needbytes = 8;
                 this.curhandlerstep = 1;

                 break;

             case 1: // process header
                 var rawdata = this.rtndata.slice(0, 8);
                 this.rtndata = this.rtndata.slice(8, this.rtndata.length);

                 var msg = new Mesg();
                 msg.group = new DataView(new Uint8Array(rawdata.slice(0, 4)).buffer).getUint32(0);
                 msg.severity = new DataView(new Uint8Array(rawdata.slice(4, 6)).buffer).getUint16(0);
                 msg.txtlen = new DataView(new Uint8Array(rawdata.slice(6, 8)).buffer).getUint16(0);
                 this.messages.push(msg);

                 if (msg.txtlen > 0) {
                     this.needbytes = msg.txtlen;
                     this.curhandlerstep = 2;
                 }
                 else {
                     done = true;
                 }
                 break;

             case 2: // process text
                 var msg = this.messages[this.messages.length - 1];
                 var rawdata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 msg.txtval = ab2str(rawdata);
                 done = true;
        }

        if (done) {
            var msg = this.messages[this.messages.length - 1];
            if (msg.group == 0 && msg.severity == 0 && msg.txtlen == 0) {
                allDone = true;
            }
            else {
                this.needbytes = 8;
                this.curhandlerstep = 1;
            }
        }

        if (allDone) {
            logactivity("processing " + this.items.length + " messages...");

            var ms = new Message();
            ms.data = this.messages;
            ms.datatype = "array";
            ms.datacount = this.messages.length;
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXMSGS";
	    sendMessage(ws, ms);

            // setup to read next message header
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }

    };

    // PROC STACK Handler - 93
    this.appxprocstackhandler = function () {
        logactivity("***** SOCKET_PROC STACK *****, step=" + this.curhandlerstep);

        var done = false;
	
        switch (this.curhandlerstep) {
             case 0: // set up to receive list of PCBs
		 if( this.procstack )
		     this.procstacklast = JSON.parse(JSON.stringify(this.procstack));
                 this.procstack = {};
                 this.needbytes = 4;
                 this.curhandlerstep = 1;
                 break;

             case 1: // Process a PCB
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 var pcb = new DataView(new Uint8Array(rawdata).buffer).getUint32(0);
		 if( pcb == 0 ) {
		     done = true;
		 }
		 else {
		     this.procstack[pcb] = true;
		     this.needbytes = 4;
		     this.curhandlerstep = 1;
		 }
                 break;
        }

        if (done) {
	    ReleaseProcessResources( self, this.procstack, this.procstacklast );

            // setup to read next message header
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // SETFIELD Handler //85
    this.appxsetfieldhandler = function () {

        logactivity("***** SOCKET_SETFIELD *****, step=" + this.curhandlerstep);

        var done = false;

        switch (this.curhandlerstep) {
             case 0:
                 this.cursetfieldcount--;
                 var rawdata = this.rtndata.slice(0, 4);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);


                 logHexDump("resHeader", rawdata, "Resource header...");

                 this.cursetfield.row = new DataView(new Uint8Array(rawdata.slice(0, 1)).buffer).getUint8(0);
                 this.cursetfield.col = new DataView(new Uint8Array(rawdata.slice(1, 2)).buffer).getUint8(0);
                 this.cursetfield.len = new DataView(new Uint8Array(rawdata.slice(2, 4)).buffer).getUint16(0);

                 if (this.cursetfield.len > 0) {
                     this.needbytes = this.cursetfield.len;
                     this.curhandlerstep = 1;
                 }
                 else {
                     done = true;
                 }

                 break;
             case 1:
                 this.cursetfield.data = [];

                 this.cursetfield.data = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 done = true;
                 break;
        }

        if (done) {

            logHexDump("SETFIELDDATA", this.cursetfield.data, "Set Field Data Received...");

            var ms = new Message();
            ms.data = this.cursetfield;
            ms.datatype = "object";
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXSETFIELD";

	    sendMessage(ws, ms);

            logactivity("cursetfieldcount=" + this.cursetfieldcount);

            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;

        }
    };

    // Items Handler
    this.appxitemshandler = function () {
        logactivity("***** SOCKET_ITEMS *****, step=" + this.curhandlerstep);
        var itemDone = false;
        var allDone = false;

        item = function () {
            this.struct = [];
            this.data = [];
        };

        switch (this.curhandlerstep) {
             case 0: // set up to read an item block from the server
                 this.items = [];
                 this.needbytes = 12;
                 this.curhandlerstep = 1;
                 break;
             case 1: // Trim off item block just read and process it and setup to read data if any
                 var itm = new item();
                 itm.struct = this.rtndata.slice(0, 12);
                 this.rtndata = this.rtndata.slice(12, this.rtndata.length);
                 this.needbytes = new DataView(new Uint8Array(itm.struct.slice(10, 12)).buffer).getUint16(0);
                 this.items.push(itm);

                 if (this.needbytes == 0) {
                     if ((itm.struct[9] & 0x02) == 0x02) {
                         //token field, these bytes contain cachid for token field data
                         this.needbytes = 24;
                         this.curhandlerstep = 3;
                     }
                     else {
                         this.needbytes = 2;
                         this.curhandlerstep = 4;
                     }
                 }
                 else {
                     this.curhandlerstep = 2;
                 }

                 break;
             case 2: // we need to read data and that data has arrived, attach it to the last item processed
                 var itm = this.items[this.items.length - 1];
                 itm.data = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 if ((itm.struct[9] & 0x02) == 0x02) {
                     //token field, these bytes contain cachid for token field data
                     this.needbytes = 24;
                     this.curhandlerstep = 3;
                 }
                 else {
                     this.needbytes = 2;
                     this.curhandlerstep = 4;
                 }
                 break;
             case 3: // trim off and process token block
                 var itm = this.items[this.items.length - 1];
                 itm.tokendata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 this.needbytes = 2;
                 this.curhandlerstep = 4;
                 break;
             case 4: // Trim off and process the widget data length
                 var itm = this.items[this.items.length - 1];
                 itm.widget_len = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);
                 this.needbytes = itm.widget_len;

                 if (this.needbytes == 0) {
                     itm.widget_data = null;
                     this.curhandlerstep = 6;
                     this.needbytes = 4
                 }
                 else {
                     this.curhandlerstep = 5;
                 }
                 break;
             case 5: // trim off and process the widget data
                 var itm = this.items[this.items.length - 1];
                 itm.widget_data = ab2str( this.rtndata.slice(0, this.needbytes) );
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 this.curhandlerstep++;
                 this.needbytes = 4;
                 break;
             case 6: // trim off and process extra data length
                 var itm = this.items[this.items.length - 1];
                 var bn = this.needbytes;
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getInt32(0);
                 this.rtndata = this.rtndata.slice(bn, this.rtndata.length);
                 if (this.needbytes > 0) {
                     this.curhandlerstep++;
                 }
                 else {
                     if (this.needbytes < 0) {
                         itm.extrareuse = true;
                     }
                     itemDone = true;
                 }
                 break;
             case 7: // trim off and process extra data
                 var itm = this.items[this.items.length - 1];
                 itm.extradata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 itemDone = true;
                 break;
        }

        if (itemDone) {
            var itm = this.items[this.items.length - 1];
            if (itm.struct[2] == 0) {
                allDone = true;
            }
            else {
                this.needbytes = 12;
                this.curhandlerstep = 1;
            }
        }

        if (allDone) {
            var items = [];
            var i = 0;

            logactivity("processing " + this.items.length + " items...");
            while (this.items.length > 0) {
                var item = this.items.shift();
                var itm = new Item();

                itm.pos_row = item.struct[0];
                itm.pos_col = item.struct[1];
                itm.size_rows = item.struct[2];
                itm.size_cols = item.struct[3];
                itm.justification = item.struct[4];
                itm.digits_left = item.struct[5];
                itm.digits_right = item.struct[6];
                itm.options = item.struct[7];
                itm.type = item.struct[8];
                itm.special = item.struct[9];

                // sometimes we need the raw data when low order bits are used to delimit data
                // like formatted fields and date pickers that don't necessarily have an attached
                // Widget.  Widgets are processed on client, so they already have rawdata.
                itm.rawdata = item.data

                itm.data = ab2str(item.data);

                if (item.widget_data != null) {
                    itm.widget = item.widget_data;
                }

                //check itm.special for tokendata
                if ((itm.special & 0x02) == 0x02) {
                    itm.tokendata = item.tokendata;
                    itm.token_cacheid = ab2str(itm.tokendata.slice(0, 8)).trim();
                    itm.token_cache_sig = ab2str(itm.tokendata.slice(8, 16)).trim();
                    itm.token_group = itm.tokendata.slice(16, 18);
                    itm.token_app = ab2str(itm.tokendata.slice(18, 21)).trim();
                    itm.token_ap_ver = ab2str(itm.tokendata.slice(21, 23)).trim();
                    itm.token_filler = itm.tokendata.slice(23, 24);
                }

                logactivity("item " + i + " row=" + itm.pos_row + " col=" + itm.pos_col + " rows=" + itm.size_rows + " cols=" + itm.size_cols + " data=" + itm.data);
                if (itm.size_rows > 0)
                    items.push(itm);
                i++;
            }

            var ms = new Message();
            ms.data = items;
            ms.datatype = "array";
            ms.datacount = items.length;
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXITEMS";
	    sendMessage(ws, ms);

            // setup to read next essage header
            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // Receive File Handler
    this.appxreceivefilehandler = function () {
        logactivity("***** SOCKET_RECEIVE_FILE *****, step=" + this.curhandlerstep);
        switch (this.curhandlerstep) {
             case 0: // set up to read an item block from the server
                 this.needbytes = 4;
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);

                 this.curhandlerstep = 2;
                 break;
             case 2:
                 var bdlen = 0;
                 var myfilerecbytes = [];

                 myfilerecbytes = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 logactivity("my url=" + ab2str(myfilerecbytes));

                 var ms = new Message();
                 ms.data = ab2str(myfilerecbytes).trim();
                 ms.datatype = "object";
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXRECEIVEFILE";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    }

    // Widget Handler
    this.appxwidgetshandler = function () {
        logactivity("***** SOCKET_WIDGETS *****, step=" + this.curhandlerstep);

        var all_done = false;
        var widgetDone = false;

        switch (this.curhandlerstep) {
             case 0: // set up to read the widget count
                 this.widgets = [];
                 this.needbytes = 4;
                 this.curhandlerstep = 1;
                 break;
             case 1: // trim off the widget count
                 this.widgetCount = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 if (this.widgetCount == 0) {
                     all_done = true;
                 }
                 else {
                     this.needbytes = 24;
                     this.curhandlerstep = 2;
                 }

                 logactivity("widgetCount=" + this.widgetCount + " all_done=" + all_done + " needbytes=" + this.needbytes);
                 break;
             case 2: // trim off a widget block
                 var mywidget = new WidgetStructure();
                 mywidget.widget_structure_raw_data = this.rtndata.slice(0, 24);
                 this.rtndata = this.rtndata.slice(24, this.rtndata.length);

                 mywidget.type = mywidget.widget_structure_raw_data[0];
                 mywidget.filler = mywidget.widget_structure_raw_data[1];
                 mywidget.parent = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(2, 4)).buffer).getUint16(0);
                 mywidget.pos_row = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(4, 8)).buffer).getUint32(0);
                 mywidget.pos_col = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(8, 12)).buffer).getUint32(0);
                 mywidget.size_row = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(12, 16)).buffer).getInt32(0);
                 mywidget.size_col = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(16, 20)).buffer).getInt32(0);


                 mywidget.data_length = new DataView(new Uint8Array(mywidget.widget_structure_raw_data.slice(20, 24)).buffer).getUint32(0);

                 this.widgets.push(mywidget);

                 if (mywidget.data_length > 0) {
                     this.needbytes = mywidget.data_length;
                     this.curhandlerstep = 3;
                 }
                 else {
                     this.needbytes = 4;
                     this.curhandlerstep = 4;
                 }

                 logactivity(" type=" + mywidget.type + " parent=" + mywidget.parent + " row=" + mywidget.pos_row + " col=" + mywidget.pos_col + " rows=" + mywidget.size_row + " cols=" + mywidget.size_col + " dataLen=" + mywidget.data_length);
                 break;
             case 3:
                 var mywidget = this.widgets[this.widgets.length - 1];
                 mywidget.widget_data = ab2str( this.rtndata.slice(0, this.needbytes) );
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 this.needbytes = 4;
                 this.curhandlerstep = 4;
                 logactivity("widgetCount=" + this.widgetCount + " widgets.length=" + this.widgets.length + " done=" + all_done + " widget_data=" + mywidget.widget_data);
                 break;
             case 4: // trim off and process extra data length
                 var mywidget = this.widgets[this.widgets.length - 1];
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getInt32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 if (this.needbytes > 0) {
                     this.curhandlerstep++;
                 }
                 else {
                     if (this.needbytes < 0) {
                         mywidget.widget_extrareuse = true;
                     }
                     widgetDone = true;
                 }
                 break;
             case 5: // trim off and process extra data
                 var mywidget = this.widgets[this.widgets.length - 1];
                 mywidget.widget_extradata = ab2str(this.rtndata.slice(0, this.needbytes));

                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 widgetDone = true;

		 mywidget.pcb = StrGetSubstr( mywidget.widget_data, "@SPCB=", "@" );

		 var lookupHash = crypto.createHash('sha1');
		 lookupHash.update(mywidget.widget_data);
		 mywidget.datalookupid = lookupHash.digest('hex');

		 //add extradata to mongo
		 var jdata, myData;
		 var mDataRows;
		 try {
		     // Parse raw table and row data into a better format
		     jdata = this.mongoconnector.parseTableData(mywidget.widget_extradata);
		     // Parse table into row data and table specs
		     myData = this.mongoconnector.createMongoRows(self, mywidget.datalookupid, mywidget.pcb, jdata, mywidget.size_col);
		     // Put row data into server temporary database 
		     if( ! mywidget.widget_extrareuse ) {
			 self.pendingInserts++;
			 this.mongoconnector.insertappxtabledata(self,mywidget.datalookupid,myData.rows);
		     }
		     // Send table definition on to client
		     mywidget.widget_extradata = myData.table;
		 } catch (e) {
		     console.log(e);
		     console.log(e.stack);
		 }
		 
                 break;
        }

        if (widgetDone) {
            if (this.widgetCount == this.widgets.length) {
                all_done = true;
            }
            else {
                this.needbytes = 24;
                this.curhandlerstep = 2;
            }
        }

        if (all_done == true) {
            var widgets = [];
            while (this.widgets.length > 0) {
                var wx = this.widgets.shift();
                var widget_wn = new DataView(new Uint8Array(wx.widget_structure_raw_data.slice(2, 4)).buffer).getUint16(0);
                widgets.push([widget_wn, wx.widget_data, {
                    "widget_extra_data": wx.widget_extradata,
		    "widget_extra_reuse": wx.widget_extrareuse == null ? false : true,
		    "datalookupid": wx.datalookupid
                }, wx]);
            } //end while widgets

            var ms = new Message();
            ms.data = widgets;
            ms.datatype = "array";
            ms.datacount = widgets.length;
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXWIDGETS";
	    sendMessage(ws, ms);

            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

    // Screen Handler
    this.appxscreenhandler = function () {
        logactivity("***** SOCKET_UPDATE_SCREEN *****, step=" + this.curhandlerstep);

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 var rd = {};
                 rd.data = "<div id='appx_main_background' style='float:left;width:70%;'>" + "" + "</div>";
                 rd.type = "HTML";

                 var ms = new Message();
                 ms.data = rd;
                 ms.datatype = "object";
                 ms.datacount = 1;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXSCREEN";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // Attributes Handler
    this.appxattributeshandler = function () {
        logactivity("***** SOCKET_UPDATE_ATTRIBUTES *****, step=" + this.curhandlerstep);

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 var x = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 var ms = new Message();
                 ms.data = x;
                 ms.datatype = "object";
                 ms.datacount = x.length;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXATTRIBUTES";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // Extended Attributes Handler
    this.appxextraattributeshandler = function () {
        logactivity("***** SOCKET_UPDATE_EXTENDED_ATTRIBUTES *****, step=" + this.curhandlerstep);

        switch (this.curhandlerstep) {
             case 0:
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 2)).buffer).getUint16(0);
                 this.rtndata = this.rtndata.slice(2, this.rtndata.length);
                 this.curhandlerstep = 1;
                 break;
             case 1:
                 var x = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);

                 var ms = new Message();
                 ms.data = x;
                 ms.datatype = "object";
                 ms.datacount = x.length;
                 ms.hasdata = "yes";
                 ms.haserrors = "no";
                 ms.type = "APPXEXTRAATTRIBUTES";
		 sendMessage(ws, ms);

                 this.curhandler = 0;
                 this.needbytes = 8;
                 this.curhandlerstep = 0;
                 break;
        }
    };

    // Ping Handler
    this.appxpinghandler = function () {
        logactivity("***** SOCKET_PING *****");

        var ms = new Message();
        ms.data = [];
        ms.datatype = "object";
        ms.datacount = 1;
        ms.hasdata = "no";
        ms.haserrors = "no";
        ms.type = "APPXPING";
	sendMessage(ws, ms);

        this.curhandler = 0;
        this.needbytes = 8;
        this.curhandlerstep = 0;
    };

    // Show Handler
    this.appxshowhandler = function () {

        logactivity("***** SOCKET_SHOW *****, step=" + this.curhandlerstep);

        var all_done = false;
        var boxDone = false;
        switch (this.curhandlerstep) {
             case 0:
                 this.current_show = new ShowStructure();

                 this.current_show.rawdata = this.rtndata.slice(0, 56);
                 this.rtndata = this.rtndata.slice(56, this.rtndata.length);

                 this.current_show.termid = this.current_show.rawdata.slice(0, 4);
                 this.current_show.curraction = this.current_show.rawdata.slice(4, 5);
                 this.current_show.filler1 = this.current_show.rawdata.slice(5, 8);
                 this.current_show.keymap = this.current_show.rawdata.slice(8, 12);
                 this.current_show.cursorrow = this.current_show.rawdata.slice(12, 16);
                 this.current_show.cursorcol = this.current_show.rawdata.slice(16, 20);
                 this.current_show.timeout = new DataView(new Uint8Array(this.current_show.rawdata.slice(20, 24)).buffer).getUint32(0);
                 this.current_show.charatcursor = this.current_show.rawdata.slice(24, 28);
                 this.current_show.useroption = this.current_show.rawdata.slice(28, 32);
                 this.current_show.rtnstatus = this.current_show.rawdata.slice(32, 36);
                 this.current_show.numboxes = this.current_show.rawdata.slice(36, 40);
                 this.current_show.numwdgts = this.current_show.rawdata.slice(40, 44);
                 this.current_show.altcursorrow = null;
                 this.current_show.altcursorcol = null;
                 this.current_show.altuseroption = null;

                 this.current_show.num_boxes = new DataView(new Uint8Array(this.current_show.numboxes).buffer).getUint32(0);
                 this.current_show.boxes = [];

                 logactivity("timeout=" + this.current_show.timeout + " action=" + this.current_show.curraction + " num_boxes=" + this.current_show.num_boxes);
                 if (this.current_show.num_boxes == 0) {
                     all_done = true;
                 }
                 else {
                     this.needbytes = 22;
                     this.curhandlerstep = 1;
                 }

                 break;
             case 1:
                 var box = new TMBoxStructure();

                 box.begin_row = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getUint32(0);
                 box.begin_column = new DataView(new Uint8Array(this.rtndata.slice(4, 8)).buffer).getUint32(0);
                 box.end_row = new DataView(new Uint8Array(this.rtndata.slice(8, 12)).buffer).getUint32(0);
                 box.end_column = new DataView(new Uint8Array(this.rtndata.slice(12, 16)).buffer).getUint32(0);
                 box.bit_mask = new DataView(new Uint8Array(this.rtndata.slice(16, 20)).buffer).getUint32(0);
                 box.data_length = new DataView(new Uint8Array(this.rtndata.slice(20, 22)).buffer).getUint16(0);

                 this.rtndata = this.rtndata.slice(22, this.rtndata.length);

                 this.current_show.boxes.push(box);
                 logactivity("box.data_len=" + box.data_length + " num_boxes=" + this.current_show.num_boxes + " boxes.length=" + this.current_show.boxes.length);
                 if (box.data_length > 0) {
                     this.needbytes = box.data_length;
                     this.curhandlerstep = 2;
                 }
                 else {
                     this.needbytes = 4;
                     this.curhandlerstep = 3;
                 }
                 break;
             case 2:
                 var box = this.current_show.boxes[this.current_show.boxes.length - 1];
                 box.data = ab2str( this.rtndata.slice(0, box.data_length) );
                 this.rtndata = this.rtndata.slice(box.data_length, this.rtndata.length);
                 this.needbytes = 4;
                 this.curhandlerstep = 3;
                 break;
             case 3: // trim off and process extra data length
                 var box = this.current_show.boxes[this.current_show.boxes.length - 1];
                 this.needbytes = new DataView(new Uint8Array(this.rtndata.slice(0, 4)).buffer).getInt32(0);
                 this.rtndata = this.rtndata.slice(4, this.rtndata.length);
                 if (this.needbytes > 0) {
                     this.curhandlerstep++;
                 }
                 else {
                     if (this.needbytes < 0) {
                         box.extrareuse = true;
                     }
                     boxDone = true;
                 }
                 break;
             case 4: // trim off and process extra data
                 var box = this.current_show.boxes[this.current_show.boxes.length - 1];
                 box.extradata = this.rtndata.slice(0, this.needbytes);
                 this.rtndata = this.rtndata.slice(this.needbytes, this.rtndata.length);
                 boxDone = true;
                 break;
        }

        if (boxDone) {
            if (this.current_show.num_boxes == this.current_show.boxes.length) {
                all_done = true;
            }
            else {
                this.needbytes = 22;
                this.curhandlerstep = 1;
            }
        }

        if (all_done) {

            var ms = new Message();
            ms.data = this.current_show;
            ms.datatype = "object";
            ms.datacount = 1;
            ms.hasdata = "yes";
            ms.haserrors = "no";
            ms.type = "APPXSHOW";
	    var waitfor = function() {
		if( self.pendingInserts == 0 ) {
		    sendMessage(ws, ms);
		}
		else {
		    setTimeout(waitfor,100);
		}
	    }

	    waitfor();

            this.curhandler = 0;
            this.needbytes = 8;
            this.curhandlerstep = 0;
        }
    };

} //APPXProcessor. object


// APPX File Object
function FileStructure() {
    var self = this;
    self.data = [];
    self.datalength = null;
    self.filenamelength = null;
    self.filename = null;
    self.currdatachunklength = 0;
    self.currdatachunk = [];
    self.filename = null;
}

// Item Object
function Item() {
    var self = this;
    self.pos_row = null;
    self.pos_col = null;
    self.size_rows = null;
    self.size_cols = null;
    self.justification = null;
    self.digits_left = null;
    self.digits_right = null;
    self.options = null;
    self.type = null;
    self.special = null;
    self.data = null; //String converted using ab4str()
    self.rawdata = null; //raws bytes in cases where data in encoded with low order bytes like formatted fields or date picker with masks
    self.widget = null;
}

function Mesg() {
    var self = this;
    self.group = null;
    self.severity = null;
    self.txtlen = null;
    self.txtval = null;
}

// Java Object Class
function JavaObject() {
    var self = this;
    self.createobjectstructurebytes = null;
    self.prototypelength = null;
    self.prototypedata = null;
    self.objecthandle = null;
    self.argumentcount = null;
    self.argumentstructurebytes = null;
    self.argumentdatatype = null;
    self.argumentdatalength = null;
    self.argumentdata = null;
    self.arguments = [];
}

// APPX Menu Object
function Menu() {
    var self = this;
    self.items = [];
}

// APPX Menu Object
function MenuItem() {
    var self = this;
}

// APPX Message Object
function Message() {
    var self = this;
    self.data = [];
    self.hasdata = null;
    self.haserrors = null;
    self.count;
    self.messagepart = 0;
    self.type = null;
}

function TokenStructure() {
    var self = this;

    this.rawdata = null;

    this.ap = null;
    this.ver = null;
    this.group = null;
    this.cacheid = null;
    this.state = 0;

    this.loctype = 0;
    this.ext = 0;
    this.len = 0;
    this.data = [];

    this.id = 0;
    this.ctx = 0;
    this.type = 0;
    this.filler = null;
    this.items = [];

}

function TokenItem() {
    var self = this;
    self.token_id = null;
    self.token_id_group = null;
    self.token_id_val_len = null;
    self.data = "";
}

function SetFieldStructure() {
    var self = this;
    self.row = null;
    self.col = null;
    self.len = null;
    self.data = "";
}

function ResourceStructure() {
    var self = this;
    this.rawdata = null;
    this.ap = null;
    this.ver = null;
    this.cacheid = null;
    this.state = 0;
    this.loctype = 0;
    this.ext = 0;
    this.len = 0;
    this.data = [];
    this.id = 0;
    this.ctx = 0;
    this.type = 0;
    this.filler = null;
}

function ShowStructure() {
    var self = this;
    self.termid = null;
    self.curraction = null;
    self.filler1 = null;
    self.keymap = null;
    self.cursorrow = null;
    self.cursorcol = null;
    self.timeout = null;
    self.charatcursor = null;
    self.useroption = null;
    self.rtnstatus = null;
    self.numboxes = null;
    self.numwdgts = null;
    self.altcursorrow = null;
    self.altcursorcol = null;
    self.altuseroption = null;
    self.rawdata = null;
    self.boxdata = null;
    self.boxes = [];
    self.num_boxes = 0;
    self.processed_boxes = 0;
}

function TMBoxStructure() {
    var self = this;
    this.begin_row = 0;
    this.end_row = 0;
    this.begin_column = 0;
    this.end_column = 0;
    this.bit_mask = 0;
    this.data_length = 0;
    this.data = [];
    this.widget = null;
    this.items = [];
    this.widgets = [];
    this.rowtext = [];
};

// WidgetStruction Object
function WidgetStructure() {
    var self = this;
    self.type = null;
    self.filler = null;
    self.parent = null;
    self.pos_row = null;
    self.pos_col = null;
    self.size_rows = null;
    self.size_cols = null;
    self.data_length = null;
    self.widget_structure_raw_data = null;
    self.widget_data = null;
    self.num_widgets = 0;
    self.processed_widgets = 0;
    self.table_data = 0;
    self.table_data_length = 0;

}

// Utilities

/*
**Function to replace the CKEDITOR config file with updated parameters that the
**newer versions of CKEDITOR require.
**
**@param editor: CKEDITOR config string.
**
**@return editor: CKEDITOR config string.
*/
function modifyCkeditor(editor){
    editor = editor.replace(/[+][ ]*\n*/g,"").trim();
    
    if (editor.indexOf("CKEDITOR.config") !== -1){
	editor = editor.replace(/CKEDITOR\.config[ ]*=[\n]*[\{]/,"CKEDITOR.editorConfig = function(config){").replace(/^[ \t]+([^:]*)[ ]?:/gm,"config.$1 =").replace(/,[ \n]*$/gm,";");
    }

    editor = editor.replace(/\r/g,"").replace(/\t/g,"").trim();  
    return editor;
}


function ab2int32(buf) {
    return new DataView(new Uint8Array(buf).buffer).getUint32(0);
}

// Convert buffer to string
function ab2str(buf) {
    var str = decoder.write(new Buffer(buf));
    return str;
}

// Convert buffer to string
function ab3str(buf) {
    return String.fromCharCode.apply(" ", new Uint8Array(buf)) + " <br/>";
}

// Convert buffer to string
function ab4str(array) {
    var string = "";
    for (var i = 0; i < array.length; i++) {
        if (array[i] != 0) {
            string += String.fromCharCode(array[i]);
        }
        else {
            string += " ";
        }
    }
    return string + " ";
}

// Convert buffer to string
function buf2str(buf) {
    var str = '';
    var charcode;
    logactivity("got buffer:  " + JSON.stringify(buf));
    for (var i = 0; i < buf.length; i++) {
        charcode = buf.readUInt8(i);
        if (charcode == 0) {
            str += " ";
        }
        else {
            str += String.fromCharCode(charcode);
        }
    }
    buf = [];
    return str;
}

// Convert hex to ascii
function hex2a(hex) {
    var str = '';
    for (var i = 0; i < hex.length; i += 2) {
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    }
    return str;
}

// Convert string to buffer
function str2ab(str, length) {
    var buf = new Uint16Array(length); // 2 bytes for each char
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf[i] = str.charCodeAt(i);
    }
    return buf;
}

// Convert string to buffer
function str3ab(str, length) {
    var buf = new Uint8Array(length); // 2 bytes for each char
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf[i] = str.charCodeAt(i);
    }
    return buf;
}

// Convert string to buffer
function str4ab(str) {
    var buf = [];
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        buf.push(str.charCodeAt(i));
    }
    return buf;
}

function base64Array(bytes) { // arrayBuffer
    var base64    = '';
    var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
    var byteLength    = bytes.length;
    var byteRemainder = byteLength % 3;
    var mainLength    = byteLength - byteRemainder;
    
    var a, b, c, d;
    var chunk;
    
    // Main loop deals with bytes in chunks of 3
    for (var i = 0; i < mainLength; i = i + 3) {
	// Combine the three bytes into a single integer
	chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
	
	// Use bitmasks to extract 6-bit segments from the triplet
	a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
	b = (chunk & 258048)   >> 12; // 258048   = (2^6 - 1) << 12
	c = (chunk & 4032)     >>  6; // 4032     = (2^6 - 1) << 6
	d = chunk & 63;               // 63       = 2^6 - 1
	
	// Convert the raw binary segments to the appropriate ASCII encoding
	base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
    }
    
    // Deal with the remaining bytes and padding
    if (byteRemainder == 1) {
	chunk = bytes[mainLength];
	
	a = (chunk & 252) >> 2; 
	
	// Set the 4 least significant bits to zero
	b = (chunk & 3)   << 4; 
	
	base64 += encodings[a] + encodings[b] + '==';
    } else if (byteRemainder == 2) {
	chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
	
	a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
	b = (chunk & 1008)  >>  4; // 1008  = (2^6 - 1) << 4
	
	// Set the 2 least significant bits to zero
	c = (chunk & 15)    <<  2; // 15    = 2^4 - 1
	
	base64 += encodings[a] + encodings[b] + encodings[c] + '=';
    }
    
    return base64;
}

// Table Data Handler
function appxTableDataHandler() {
    logactivity("***** TABLE_DATA_HANDLER *****");
    
    var self = this;

    // connecttodb()
    self.connecttodb = function () {
    };

    // disconnectfromdb()
    self.disconnectfromdb = function () {
    };

    // clearallappxtabledata()
    self.clearallappxtabledata = function (coll, res) {
	try {
	    mongoCacheDb.collection(coll, function (err, collection) {
		    if( collection )
			collection.drop();
	    });
	} catch (e) {
	    console.log(e);
	    console.log(e.stack);
	}
    };

    // removeappxtabledata()
    self.removeappxtabledata = function (lookupid, res) {
	try {
	    mongoCacheDb.collection(self.cacheCollection, function (err, collection) {
		if( ! err )
		    collection.remove({ "datalookupid": lookupid },function(err,result){
		    });
	    });
	} catch (e) {
	    console.log(e);
	    console.log(e.stack);
	}
    };

    // releaseappxtabledata()
    self.releaseappxtabledata = function (coll, pcb, res) {
	try {
	    mongoCacheDb.collection(coll, function (err, collection) {
		if( ! err )
		    collection.remove({ "datapcbid": pcb },function(err,result){
		    });
	    });
	} catch (e) {
	    console.log(e);
	    console.log(e.stack);
	}
    };

    // insertappxtabledata()
    self.insertappxtabledata = function (appxprocessor, lookupid, message, res) {
	try {
	    mongoCacheDb.collection(appxprocessor.cacheCollection, function (err, collection) {
		if( ! err && message.length > 0 ) { 
		    collection.remove({ "datalookupid": lookupid },function(err,res) {
			if( ! err ) {
			    var released = false;
			    var bulk = collection.initializeUnorderedBulkOp();
			    for( var i = 0; i < message.length; i++ ) {
				bulk.insert(message[i]);
			    }
			    bulk.execute({},function(err,res) {
				appxprocessor.pendingInserts--;
			    });
			}
		    });
		}
	    });
	} catch (e) {
	    console.log(e);
	    console.log(e.stack);
	}
    };
    
    //we may need to make this generic and put in utils lib
    self.parseTableData = function(tabledata) {
	try {
	    // Convert buffer to string
	    var newstring = ab2str(tabledata);
	    return JSON.parse('{"tabledata": [' + newstring + ']}');
	}
	catch (ex) {
	    console.log("parseTableData() failed: " + ex);
	    console.log(ex.stack);
	    return "";
	}
    };
    
    self.createMongoRows = function (appxprocessor, datalookupid, datapcbid, dataobject, columnSize) {
	try {
	    var data = dataobject.tabledata;
	    var numrows = parseInt(data[0][0]);
	    var numcols = parseInt(data[0][1]);
	    var expand = data[0][2];
	    var floatcol = parseInt(data[0][2]) - 1;
	    var colsarray = data.slice(1, numcols + 1);
	    var sortcols = [];
	    var rowdata = data.slice(numcols + 1, data.length);
	    var widthcur = 0;
	    
	    // Build list of which columns have extra hidden sort columns
	    sortcols.push(null);
	    sortcols.push(null); // id column
	    sortcols.push(null); // selection column
	    if (colsarray.length > 0) {
		for (var i = 0; i < colsarray.length; i++) {
		    var cleanName = colsarray[i][3].replace(/\'/g, "").replace(/\s/g, "_").replace(/\./g, "_").toLowerCase();
		    switch(colsarray[i][4]) {
			 case "java.lang.Integer": cleanName += "I"; sortcols.push("I"); break;
			 case "java.lang.Float":   cleanName += "F"; sortcols.push("F"); break;
			 case "java.util.Date":    cleanName += "D"; sortcols.push("D"); break;
			 case "java.lang.Boolean": cleanName += "B"; sortcols.push(null);break;
			 default: 
			     cleanName += "A";
			     sortcols.push("A"); 
			     break;
		    }
		    colsarray[i]["cleanName"] = cleanName;
		}
	    }
	    
	    /////////////////////////////////////////////////////
	    // Create the column models and names for the grid //
	    /////////////////////////////////////////////////////
	    
	    var colnames = {};
	    var colmodel = [];
	    var sortcolmap = {};
	    var colopts = [];
	    var collist = { "id2": 1, "selected": 1 };

	    //Create hidden initial sort column
	    colmodel.push({
		"name": "initialSort",
		"index": "initialSort",
		"width": 1,
		"hidden": true,
	    });
	    
	    // Create hidden row id column
	    colmodel.push({
		"name": "id2",
		"index": "id2",
		"width": 1,
		"hidden": true,
		"key": true
	    });
	    
	    // Create hidden selection column
	    colmodel.push({
		"name": "selected",
		"index": "selected",
		"width": 1,
		"hidden": true
	    });

	    
	    if (floatcol >= 0) {
		for (var i = 0, j = 3; i < colsarray.length; i++, j++) {
		    var cellWidth = (parseInt(colsarray[i][2]) * appxprocessor.colWidthPx);
		    if (i != floatcol)
			widthcur += cellWidth;
		}
	    }
	    
	    var inc = 1;
	    
	    for (var i = 0, j = 3; i < colsarray.length; i++, j++) {
		if( colnames.hasOwnProperty(colsarray[i]["cleanName"]) ) {
		    if( inc == 1 )
			colsarray[i-1]["cleanName"] += "_"+inc;
		    colsarray[i]["cleanName"] += "_"+(++inc);
		}
		else {
		    inc = 1;
		}

		var cleanName = colsarray[i]["cleanName"];

		var floatCol;
		if (i != floatcol){
		    floatCol = true;
		}else{
		    floatCol = false;
		}

		collist[cleanName] = 1;

		// Create the column definition
		var colObject = {
		    "name": cleanName,
		    "index": cleanName,
		    "label": colsarray[i][3],
		    "width": (parseInt(colsarray[i][2]) * appxprocessor.colWidthPx),
		    "fixed": floatCol,
		    "align": "left",
		    "sortable": true,
		    "title": false,
		    "datatype": "html"
		    
		};

		// Let's go ahead and push this into the column model list but
		// we can still override it's values as needed.
		colmodel.push(colObject);
		
		if( colsarray[i][4].indexOf("Boolean") > -1 ) {
		    colObject["formatter"] = "checkbox";
		    colObject["editoptions"] = {value: "y:n"};
		    colObject["align"] = "center";
		    colObject["formatoptions"] = {disabled: true};
		}
		else if( colsarray[i][4].indexOf("Integer") > -1 ) {
		    colObject["align"] = "right";
		    colObject["searchtype"] = "integer";
		}
		else if( colsarray[i][4].indexOf("Float") > -1 ) {
		    colObject["align"] = "right";
		    colObject["searchtype"] = "float";
		}
		
		if (sortcols[i+3]) {
		    var sortname = "sortcol_" + (i+3).toString() + colsarray[i]["cleanName"].slice(-1);

		    colObject["index"] = sortname;

		    colmodel.push({
			"name": sortname,
			"index": sortname,
			"width": 0,
			"hidden": true
		    });
		}
	    }


	    
	    var mydata = rowdata;
	    var newrow = [];
	    var jsonRows = [];
	    var selectedRows = [];
	    var selectedKeys = [];

	    //If no rows are returned, push empty row with modified datalookupid
	    if (mydata.length == 0){
		var myrow = {};
		for (var j = 0; j < colmodel.length; j++) {
		    myrow[colmodel[j].name] = newrow[j];
		}
		myrow.datalookupid = "BlankTable";
		myrow.datapcbid = datapcbid;
		myrow["_id"] = datalookupid + "_" + myrow.initialSort;
		jsonRows.push(myrow);
	    }

	    
	    for (var i = 0; i < mydata.length; i++) {
		newrow = [];
		mydata[i].unshift(i);
		for (var k = 0; k < mydata[i].length; k++) {
		    var d = [];
		    if (k == 0){
			d[0] = mydata[i][k];
		    }else{
			d = mydata[i][k].split("||");
		    }
		    

		    //If contains HTML data push through otherwise replace < > symbols.
		    if (d[0].length > 5 && d[0].substr(0, 5).toLowerCase() == "<html"){
			newrow.push(d[0]);
		    }else {
			if ( (k>2) && (colsarray[k-3][4] == "java.lang.Boolean") && (d[0] == " ")){
			    newrow.push("n");
			}else if (k != 0){
			    newrow.push(d[0].replace(/\</g, "&lt;").replace(/\>/g,"&gt;"));
			}else{
			    newrow.push(d[0]);
			}
		    }
		    
		    if (sortcols[k]) {
			switch(sortcols[k]) {
			     case "I":
			     case "D":
				 if (d.length > 1) 
				     newrow.push(parseInt(d[1]));
				 else 
				     newrow.push(0);
				 break;
			     case "F":
				 if (d.length > 1) 
				     newrow.push(parseFloat(d[1]));
				 else 
				     newrow.push(0.0);
				 break;
			     case "A":
				 if (d[0].length > 5 && d[0].substr(0, 5).toLowerCase() == "<html"){
			             newrow.push(StringLib(d[0]).stripTags().toString());
				 }
				 else {
				     newrow.push(d[0]);
				 }
				 break;
			}
		    }
		}
		
		var myrow = {};
		for (var j = 0; j < colmodel.length; j++) {
		    myrow[colmodel[j].name] = newrow[j];
		}
		if( myrow.selected == "true" ) {
		    selectedRows.push(i+1);
		    selectedKeys.push(myrow.id2);
		}

		myrow.datalookupid = datalookupid;
		myrow.datapcbid = datapcbid;
		myrow["_id"] = datalookupid + "_" + myrow.initialSort;
		jsonRows.push(myrow);
		
	    }

	    var mRtn = {};
	    mRtn.rows = jsonRows;
	    mRtn.table = {};
	    mRtn.table.collection = appxprocessor.cacheCollection;
	    mRtn.table.datalookupid = datalookupid;
	    mRtn.table.rowCount = numrows;
	    mRtn.table.colCount = numcols;
	    mRtn.table.expand = expand;
	    mRtn.table.colModel = colmodel;
	    mRtn.table.sortColMap = sortcolmap;
	    mRtn.table.floatcol = floatcol;
	    mRtn.table.widthcur = widthcur;
	    mRtn.table.selectedRows = selectedRows;
	    mRtn.table.selectedKeys = selectedKeys;
	    mRtn.table.collist = JSON.stringify(collist);
	    return mRtn;
	}
	catch (e) {
	    console.log(e);
	    console.log(e.stack);
	}
	
    };
};

function StrGetSubstr( str, prefix, suffix ) {
    var idx1 = str.indexOf(prefix);
    var str1 = str.substr(idx1+prefix.length);
    var idx2 = str1.indexOf(suffix);
    var str2 = str1.substr(0,idx2);
    return str2;
}

function ReleaseProcessResources( appxprocessor, procstack, procstacklast ) {
    for (var property in procstacklast) {
	if (procstacklast.hasOwnProperty(property)) {
	    if( (Object.keys(procstack).length > 0) && !procstack[property] ) {
		appxprocessor.mongoconnector.releaseappxtabledata( appxprocessor.cacheCollection, property );
	    }
	}
    }    
}

function sendMessage( ws, msgData ){
    ws.send2(JSON.stringify(msgData));
}
