
  var LogServiceCallString = '';
  var LogServiceHeaderString = '';
  var startTime;
  var endTime;
  var previousState = 'UNKNOWN';
  var callId;

  
  // Top-level function: Adds a header entry to the call log
  function addLogHeader(headerID, headerValue) {
    LogServiceHeaderString += headerID + '=' + headerValue + ';';
  }

  // Top-level function: Adds a detail record to the call log
  function addLogDetail(stateID, eventCode) {
    if (stateID == '') {
      stateID = '9999';
    }

    if(stateID.toLowerCase() == 'previous_state') {
        stateID = getPreviousState();
    }
    else {
        setPreviousState(stateID);
    }

    var logEntry = stateID + '|' + eventCode + '|' + generateTimeStamp( new Date() );

    //remove semi colons, newlines, and carriage return
    logEntry = logEntry.replace(/;/g, ':');
    logEntry = logEntry.replace(/\r/g, ' ');
    logEntry = logEntry.replace(/\n/g, ' ');

    LogServiceCallString += logEntry + ';';
  }
  
  // Encode the object [eventObj] with specified [delimeter] and [maxDepth]
  // The last parameter must be [0] for recursive calls to track properly
  function encodeObject( eventObj ) {
    return(encodeObjectCustom(eventObj, ':', 3, 0));
  }

  // Encode [eventObj] into property=value pairs delimited by [delimeter], surrounded by brackets
  function encodeObjectCustom( eventObj, delimeter, maxDepth, curDepth ) {
      if ((typeof eventObj == 'object') && (curDepth < maxDepth)) {

          // if the object is an array . . .
          if(eventObj.constructor.toString().match('Array') != null) {
          
              // mark the start of an array list
              var arrayObjString = '{';
              
              for(var x = 0; x < eventObj.length; x++) {
                  if (typeof eventObj[x] == 'object') {
                      arrayObjString += encodeObjectCustom(eventObj[x], delimeter, maxDepth, (curDepth + 1)) + delimeter;
                  }
                  else {
                      arrayObjString += '[' + encodeObjectCustom(eventObj[x], delimeter, maxDepth, (curDepth + 1)) + ']' + delimeter;
                  }
              }
              
              //remove trailing delimeter from the object list
              if (arrayObjString.substring(arrayObjString.length-1,arrayObjString.length) == delimeter) {
                  arrayObjString = arrayObjString.substr(0,arrayObjString.length-1);
              }
              
              // mark the end of the array list
              arrayObjString += '}' + delimeter;
              
              //remove trailing delimeter from the array list
              if (arrayObjString.substring(arrayObjString.length-1,arrayObjString.length) == delimeter) {
                  arrayObjString = arrayObjString.substr(0,arrayObjString.length-1);
              }
              
              return arrayObjString;
          }
          else {
              // mark the begining of the object  
              var objString = '[';
              var slot;
              
              for (slot in eventObj) {
              
                  // if the item is an object
                  if (typeof eventObj[slot] == 'object') {
                  
                      // if the item is an array
                      if (eventObj[slot].constructor.toString().match('Array') != null) {
                      
                          // mark the begining of an array (array is a 'value' in a key/value pair of the object)
                          objString += slot + '={';
                          
                          for(var y = 0; y < eventObj[slot].length; y++) {
                              objString += encodeObjectCustom(eventObj[slot][y], delimeter, maxDepth, (curDepth + 1));
                              if(y < eventObj[slot].length - 1) {
                                  objString += delimeter;
                              }
                          }
                          
                          // remove trailing delimeter
                          if (objString.substring(objString.length-1,objString.length) == delimeter) {
                              objString = objString.substr(0,objString.length-1);
                          }
                          
                          // close array
                          objString += '}' + delimeter;
                          
                      } // if (item is array)
                      else {
                          objString += slot + '=' + encodeObjectCustom(eventObj[slot], delimeter, maxDepth, (curDepth + 1)) + delimeter;
                      }
                  } // if (item is object)
                  
                  // else item plain data
                  else {
                      
                      //Normalize Data
                      var thisData = normalizeData(eventObj[slot]);
                      
                      //Look For Certain Keys to Mask
                      if(slot == 'Account' || slot == 'AccountNum' || slot == 'AccountNumber') {
                        thisData = mask(thisData); 
                      }
                      
                      objString += slot + '=' + thisData + delimeter;
                  }
              }
              
              // remove trailing delimeter
              if (objString.substring(objString.length-1,objString.length) == delimeter) {
                  objString = objString.substr(0,objString.length-1);
              }
              
              // close the object
              objString += ']';
              
              return objString;
          }
      }
      
      // if we're passed the maxDepth or item is not an object, just return it
      else {
          return normalizeData(eventObj);
      }
  }
  
  function normalizeData ( x ) {
      //remove commas, colons, semi colons, newlines, and carriage returns from strings
      if (typeof x == 'string') {
        x = x.replace(/,/g, ' ');
        x = x.replace(/:/g, ' ');
        x = x.replace(/;/g, ' ');
        x = x.replace(/\r/g, ' ');
        x = x.replace(/\n/g, ' ');
      }
      return x;
  }


  // As per the VUI spec this generates the current time in the form: yyyyMMdd.kk:mm:ss.SSS
  function generateTimeStamp( d ) {
    var yearString = d.getFullYear();
    // Milliseconds can be derived from the getTime function
    var milliString = LPad(d.getTime() % 1000, '0', 3);
    var dateString;

    dateString = yearString + LPad(d.getMonth() + 1, '0', 2) + LPad(d.getDate(), '0', 2);
    dateString = dateString + '.' + LPad(d.getHours(), '0', 2) + ':' + LPad(d.getMinutes(), '0', 2);
    dateString = dateString + ':' + LPad(d.getSeconds(), '0', 2) + '.' + milliString;
    return dateString;
  }
  
  function generateTodaysDate( d ) {
	    var yearString = d.getFullYear();
	    // Milliseconds can be derived from the getTime function
	    
	    var dateString;

	    dateString = (d.getMonth() + 1, '0', 2) +'/'+ LPad(d.getDate(), '0', 2) + '/'+ LPad(yearString, '2', 2);
	    
	    return dateString;
	  }

  // Used by generateTimeStamp to pad strings out to a given length
  function LPad(oldString, padChar, fieldLength) {
    var newString = '' + oldString; // Force the new var to a string
    var strLength = newString.length;
    var i;

    for (i = strLength; i < fieldLength; i++) {
        newString = '' + padChar + newString;
    }

    return newString;
  }

  // Set the required CALLID header for this call
  function setCallId( machineId, channel, trunk ) {
    var startTimeStamp;

    if (typeof startTime == undefined) {
      startTime = new Date();
    }

    startTimeStamp = generateTimeStamp( startTime );

    callId = machineId + '.' + channel;

    if (trunk != '') {
      callId += '.' + trunk;
    }

    callId += '.' + startTimeStamp;

    addLogHeader('CALLID', callId);
  }

  // Get the CALLID header for this call
  function getCallId() {
    return callId;
  }

  function endCall() {
    LogServiceCallString += ";";
    
    endTime = new Date();
    addLogHeader( 'ENDTIME', generateTimeStamp(endTime) );
    
    if (typeof startTime == undefined) {
      startTime = new Date();
    }
    
    var totalTime = endTime.getTime() - startTime.getTime();

    // compute minutes:seconds.milliseconds of total call time
    var hours = Math.floor(totalTime / (60 * 60 * 1000));
    totalTime -= hours * (60 * 60 * 1000);
    var minutes = Math.floor(totalTime / (60 * 1000));
    totalTime -= minutes * (60 * 1000);
    var seconds = Math.floor(totalTime / 1000);
    totalTime -= seconds * 1000;
    var milliseconds = totalTime;

    var totalTimeStamp = LPad(minutes, '0', 2) + ':' + LPad(seconds, '0', 2) + '.' + LPad(milliseconds, '0', 3);
    addLogHeader( 'TOTALTIME', totalTimeStamp );
  }

  // substitute a logging param into a log string
  function replaceParam(logString, paramKey, paramValue) {
    var replaceExpr = new RegExp('%'+paramKey);
    var newLogString = logString.replace(replaceExpr, paramValue);
    return newLogString;
  }

  function getCallLogString() {
    return LogServiceCallString;
  }

  function getHeaderString() {
    return LogServiceHeaderString;
  }

  function getCompleteLogString() {
    return LogServiceHeaderString + ';' + LogServiceCallString;
  }

  function addCallLogString( logString ) {
    LogServiceCallString += logString;
  }

  function createLogEventString(message, event) {
    return (message + ';;' + getPreviousState() + ";;" + LogServiceCallString + ";" + event);
  }

  function getLogEventString(logString) {
    var logEventString = '';
    var messageParts = logString.split(';;');
    if(messageParts.length == 4) {
      setPreviousState(messageParts[1]);
      logEventString = messageParts[2] + ';';
    }
    return logEventString;
  }

  function getLogMessage(logString) {
    var messageParts = logString.split(';;');
    if(messageParts.length == 4) {
      return messageParts[0];
    }
    else {
      return '';
    }
  }

  function getLogEvent(logString) {
    var messageParts = logString.split(';;');
    if(messageParts.length == 4) {
      return messageParts[3];
    }
    else {
      return '';
    }
  }

  function setStartTime( time ) {
    startTime = time;
  }

  function getStartTime() {
    return startTime;
  }

  function setEndTime( time ) {
    endTime = time;
  }

  function getEndTime() {
    return endTime;
  }

  function setPreviousState(pState) {
    previousState = pState;
  }

  function getPreviousState() {
    return previousState;
  }

  //Adds VISTA string returned from subdialogs to main string and returns
  //message portion.
  function parseSubdialogVista(returnString) {
    if((typeof(returnString) != 'undefined') && (returnString != null) && (returnString != '')) {
        addCallLogString(getLogEventString(returnString));
        return getLogMessage(returnString);
    }
    else {
      return '';
    }
  }
  
  function removeLastEvent() {
    var indx = LogServiceCallString.lastIndexOf(';', LogServiceCallString.length-2);
    LogServiceCallString = LogServiceCallString.substring(0, indx+1);
  }
  
  function mask(strVal) {
    var maskedStr = strVal;
    if(strVal != null && strVal != '') {
      if(strVal.length > 4) {
        var startStr = strVal.substring(0, strVal.length-4);
        var last4 = strVal.substring(strVal.length-4);
        maskedStr = startStr.replace(/./g, '*') + last4;
      }
    }
    return maskedStr;
  }
  
  function maskAll(strVal) {
    var maskedStr = strVal;
    if(strVal != null && strVal != '') {
      maskedStr = strVal.replace(/./g, '*');
    }
    return maskedStr;
  }
  
  function maskLast4(strVal) {
    var maskedStr = strVal;
    if(strVal != null && strVal != '') {
      if(strVal.length > 4) {
        maskedStr = strVal.substring(0, strVal.length-4) + '****';
      }
    }
    return maskedStr;
  }