//
// © 2009 Wilke/Thornton, Inc.
//
// File:     js/public/chat/publicChat.js
// Created:  2009/10/17 - Anne D. Ryan
// Modified: 2009/10/17 - Anne D. Ryan
//             - CRS-187 - Created (Chat API V2.0)
//           2010/02/08- Anne D. Ryan
//             - CRS-200 - Modified for initialText & site specific chatStatus cookie.
//                       Set chat.start error msg into chat.msg instead of alert. 
//                       Retrieve wsUrl from crslogin.com.  (Chat API V2.4)
//           2010/02/26 - Anne D. Ryan
//             - CRS-303 - Modified to work with pop-up blockers. (Chat API V2.5)
//           2010/05/14 - Anne D. Ryan
//             - CRS-375 - Modified to test for chat.start in process. (Chat API V2.6)
//           2011/01/03 - Anne D. Ryan
//            - CRS-660 - Default this.msg to this.msgUnavailable prior to getStatus call.
//                        to prevent NULL msg. (Chat API V2.7)
//           2011/02/28 - Anne D. Ryan
//            - CRS-767 - Added useInfoForm property, set via a CRS config.
//                        Allows for a CRS defined Chat Information form to be
//                        presented prior to the chat window (Chat API V2.8)
//                       
//  NOTE: JSONP script tags do not allow trapping HTTP errors, or "webspeed down" HTML error.
//        Receive script error at Line 1, because javascript has not bee returned.
//
//  NOTE:  Must be browser independant !

/*global */

Wt = function() {return};
Wt.chat = function() {return};

//
// Class Definitions
//

/**
 * @class Wt.chat.PublicChat
 * A singleton class with a number of utility functions used by CRS.
 * 
 *   A singleton class used to determine the status of the chat system 
 *   and to initiate a public chat session, 
 *   typically from a page in the client's website. 
 *    
 * @singleton
 * @namespace Wt.chat
 *   
 *  EXAMPLE USAGE: 
 *  
 *  chat = Wt.chat.PublicChat;
 *  chat.init ({
 *      site: "baby", 
 *      statusCallback: testStatus,
 *      startOptions: {
 *          window: {
 *              top: 100,
 *              left: 100,
 *              width: 400,
 *              height: 700,
 *              cssUrl: 'http://localhost/hostedpages/jockey/css/common.css',
 *              logoUrl: 'http://demo.wilke-thornton.com/habi/images/basketball.gif',
 *              headerHtml: '<p><span style="font:18pt arial;color:#666699">&nbsp;How may we help you?</span></p>',
 *              footerHtml: "<b>FOOTER</b>",
 *              title: "Chat with a Live Representative!"
 *          },
 *          form: document.getElementById("chatForm"),
 *          info: {
 *              "subject": "BOUNCE"
 *          },
 *          clickStream: ["123", "456"],
 *          shoppingCart: "176543329482"
 *      }
 *  });  
 *   
 *  cmdChat_onClick = function () {     
 *      chat.start({
 *          info: {
 *              "product": "BBALL+"
 *          }
 *      });
 *  };
 *  
 *  testStatus = function (result) {
 *      if (chat.status === "ready") {
 *          // show chat button
 *          cmdChat.style.visibility = "visible";
 *      } else if (chat.msg !== "" ) {
 *          alert (chat.msg);
 *      }
 *  };
 *  
 */
 
Wt.chat.PublicChat = function() {

    var _singleton;

    //
    // The singleton object, with its public properties and functions
    //
    
    _singleton = {

        //
        // Configuration properties
        //
    
        //
        // Public properties
        //        
        
        /**
         * @property enablePublicChat For turning ALL chat off.
         * false will force chat.status = 'offline'.
         * @type boolean
         */    
        enablePublicChat: true,
        
        /**
         * @property site
         * The value to map to a CRS Import Source
         * @type string
         */    
        site : "",
        
        /**
         * @property statusExpireMinutes
         * The number of minutes to expire the Status cookie.
         * Default = null, expires on close of browser session.         
         * @type integer
         */    
        statusExpireMinutes: null,
        
        /**
         * @property statusCallback
         * The name of the callback function for getStatus()
         * @type string
         */    
        statusCallback : "",
        
        /**
         * @property msgChatBegun
         * The message to display when chat.start is attempted 
         * before completion of the prior chat.start process.
         * May be overridden by non-blank CRS config Chat/paraChatBegun paragraph.  
         * @type string
         */    
        msgChatBegun : "Please wait for the chat to begin.",
        
        /**
         * @property msgPopUpWarn
         * The message to display when popup blocker on.
         * May be overridden by non-blank CRS config Chat/paraPopUpWarn paragraph.        
         * @type string
         */    
        msgPopUpWarn : "Please unblock pop-ups for this chat.",
        
        /**
         * @property msgUnavailable
         * The message to display when webspeed is down.
         * @type string
         */    
        msgUnavailable : "Sorry, chat is temporarily unavailable",        
    
        //
        // Private properties
        //
        
        /**
         * @property crsChatSystem       
         * The CRS chat system name. 
         *   Used to retieve wsUrl from crslogin.com/php
         * @type string
         */    
        crsChatSystem : "",
        
        /**
         * @property msg       
         * The status message retreived by getStatus or from the statusMsg cookie
         * @type string
         */    
        msg : "",
        
        /**
         * @property wsUrl
         * Ignored if crsChatSystem is non-blank.         
         * The URL of the CRS Public Chat web service, 
         *   e.g. "https://server/scripts/cgiip.exe?WService=crs96abc"
         * @type string
         */    
        wsUrl : "",
        
        /**
         * @property useInfoForm       
         * Designates if a CRS defined Info Form with enterable fields 
         * should preceed the Chat window.
         * @type boolean
         */    
        useInfoForm: null,
        
        startOptions : {},
        
        //
        // Public functions
        //
    
        /**
         * Returns the current status of the chat server: 
         * one of "ready", "closed" or "offline". 
         * Useful for modifying the appearance or behavior of the page, 
         * e.g. whether a chat button should appear or a proactive chat dialog 
         * should popup.     
         */
        getStatus: function() {   
            var params = {};  
            this.msg = "";
             
            try {
                if (this.enablePublicChat !== true) {
                    this.status = "offline";
                    this.msg = this.msgUnavailable; 
                    this._setCookie ("chatStatus" + this.site, this.status, this.statusExpireMinutes);
                    this._setCookie ("chatStatusMsg" + this.site, this.msg, this.statusExpireMinutes);
                }
            } catch(e) {}
            
            try {
                // use status from existing cookie, if available
                this.status = this._getCookie("chatStatus" + this.site);
                this.msg = this._getCookie("chatStatusMsg" + this.site);
                this.useInfoForm = this._getCookie("chatUseForm" + this.site);
                if (this.status !== null) {             
                    if ( this.msg === null ) {
                        if ( this.status !== "ready" ) { 
                            this.msg = "";  
                        } else {
                            this.msg = this.msgUnavailable;  
                        }  
                    }             
                    if (this.statusCallback) {
                        this.statusCallback.call();
                    }      
                    return;  
                }
            } catch(e) {}
            
            params.site  = ( this.site || "" );
            var urlEncodedParams = this._urlEncode(params);
            if (urlEncodedParams !== '') {
                urlEncodedParams = '&' + this._urlEncode(params);
            }
            // set default message
            this.msg = this.msgUnavailable; 
            var jsonUrl = this.wsUrl 
                        + "webspeed/public/chat/wsPublicChatStatus.w?upRespFormat=json"         
                        + urlEncodedParams;   
            this._jsonp(jsonUrl, "Wt.chat.PublicChat.getStatus_callback");
        },
        
        getStatus_callback: function (result) {  
            // possible status values = ready, closed, offline.
            
            try {
                this.status = this._evalJson(result, "status");  
                try {
                    this.useInfoForm = this._evalJson(result, "useForm");  
                } catch (e) {}
            } catch (e) {
                this.status = "offline"; 
                this.msg = this.msgUnavailable; 
            }        
            
            if ( this.status === "ready" && this.msg === this.msgUnavailable ) {
                // clear default message
                this.msg = "";
            }     
            
            this._setCookie ("chatStatus" + this.site, this.status, this.statusExpireMinutes);
            this._setCookie ("chatStatusMsg" + this.site, this.msg, this.statusExpireMinutes);
            this._setCookie ("chatUseForm" + this.site, this.useInfoForm, this.statusExpireMinutes);
         
            if (this.statusCallback) {
                this.statusCallback.call();
            }        
        },  
        
        /**
         * getWsUrl_callback
         */
        getWsUrl_callback: function (result) {
            var resultUrl = "";
            try {
                resultUrl = (this._evalJson(result, "wsUrl") || this.wsUrl);  
            } catch (e) {}  
            
            if (resultUrl !== "") {
                this.wsUrl = resultUrl;
            }
            //  make sure it ends with a slash
            if (this.wsUrl.lastIndexOf("/") + 1 < this.wsUrl.length) {
                this.wsUrl += "/";
            }
            
            this.statusCallback = (this.initParams.statusCallback || this.statusCallback);
            this.startOptions   = (this.initParams.startOptions || {} );
            this.site           = (this.initParams.startOptions.site || this.initParams.site || "" );
            this.getStatus();
        },
        
        /**
         * init
         */
        init: function(params) {
            this.initParams = params;
            this.msgUnavailable = (this.initParams.msgUnavailable || this.msgUnavailable);
            this.chatStartInProcess = false;
            if (this.crsChatSystem !== "") {
                var jsonUrl = "http://crslogin.com/php/getUrlAsJsonp.php?system="
                            + this.crsChatSystem;   
                this._jsonp(jsonUrl, "Wt.chat.PublicChat.getWsUrl_callback");
            } else {
                this.getWsUrl_callback();
            }
              //  FOR TEST ONLY  this.getWsUrl_callback("({'wsUrl':'http:\/\/localhost\/scripts\/cgiip.exe\/WService=crs96oe'})");
        },
        
        
        /**
         * Starts a chat session 
         */
        start: function(options) {            
            if (this.status !== 'ready') {return}; 
             
            var jsonUrl;
            var name;
            var objElem;
            var params = {};
            
            var strFeatures
            var strName
                
            if (this.chatStartInProcess ) {                   
                alert (this.msgChatBegun);
                return;
            }
            
            try {
                if (this.chatSessionWindow 
                && !this.chatSessionWindow.closed) { 
                    try { 
                      this.chatSessionWindow.focus(); 
                      return;
                    } catch(e) {return;}                 
                }              
                // chat session window has been closed.  Fall through to try again.     
            } catch(e) {}    
            
            var windowConfig = ( this.startOptions.window || {} );
            var top    = ( windowConfig.top || 0 );
            var left   = ( windowConfig.left || 0 );
            var width  = ( windowConfig.width || 350 );
            var height = ( window.screen.width > 800 && window.screen.height > 600 ? 540 
                         : (window.screen.availHeight - 48) );
            height = ( windowConfig.height || height );            
            
            //  defaulting toolbar=no forces IE7 to a new window instead of tab, 
            //    when "Let IE decide" is chosen for tabbed browsing pop-ups windows
            strFeatures = "status=yes,resizable=yes,scrollbars=yes"
                        + ",titlebar=yes"                    
                        + ",width=" + width  + ",height=" + height
                        + ",top=" + top  + ",left=" + left;
            
            var now = new Date();
            strName = ( windowConfig.name || "crs" + now.getTime());
            
            this.chatSessionWindow = window.open("about:blank", strName, strFeatures);
            if (this.chatSessionWindow) {               // Most browsers will not create window object when blocked
                if (this.chatSessionWindow.name === undefined) {    // Extra test since Opera defines window object then blocks
                    alert (this.msgPopUpWarn);
                    return;
                }
            }else{
                alert (this.msgPopUpWarn);
                return;
            }
            
            if ( this.useInfoForm === "true" ) {
                // Open a chat info form from CRS, 
                // which will initiate a chat session window when submitted.
                this._openChatInfoForm();
                return;
            }
            
            // Initiate a new chat session, 
            // which will return a chat session window from CRS
            this.chatStartInProcess = true;             
            try {
                this.chatSessionWindow.document.body.innerHTML = this.msgChatBegun;
            } catch(e) {}
            
            var options = (options || {});  
            options = (typeof options === "object" ? options : {});  
            
            // merge options with chat.startOptions
            var myOptions = this.startOptions;
            for (var prop in options) {  
                if (typeof myOptions[prop] === "undefined") {
                    myOptions[prop] = options[prop];
                } else {
                    for (var subprop in options[prop]) {  
                        myOptions[prop][subprop] = options[prop][subprop];
                    }
                }
            } 
            
            var aClickStream = ( myOptions.clickStream || [] );
            params.clickStream  = ( (aClickStream.toString()) || "" );
            params.initialText  = ( (myOptions.initialText) || "" );
            params.shoppingCart = ( (myOptions.shoppingCart) || "" );
            params.site = ( (myOptions.site) || this.site || "" );
            
            for (var prop in myOptions.info) {  
                params[prop] = myOptions.info[prop];
            } 
            
            if (myOptions.form) {
                var elements = myOptions.form.elements;
                for (var i = 0, len = elements.length; i < len; ++i) {
                    objElem = elements.item(i);
                    if (objElem.value) {
                        name = (objElem.id || objElem.name);
                        params[name] = objElem.value;
                    }
                }
            }
            var jsonUrl = this.wsUrl
                        + "webspeed/public/chat/wsChatsessionNew.w" 
                        + "?upRespFormat=json"
                        + "&upAction=update"
                        + "&chatAction=getMessage"
                        + "&upLock=no&"                    
                        + this._urlEncode(params);   
                                 
            this._jsonp(jsonUrl, "Wt.chat.PublicChat._postChatsessionComplete");
        },
        
        
        //
        // Event handlers
        //
    
        //
        // Private functions
        //
         
        /**
         * Copies data from the response object to the Chat Session Data object. 
         *   
         * @param {Object} response  The Ajax response object.  
         *      
         * @return {Boolean} Sucessful?
         */
        _copyDataFromServer: function(response) {        
            this.sessionData = {};
            for (var prop in response) {   
                if (prop !== "err" && prop !== "info" && prop !== "warn" ) {
                    try {
                        this.sessionData[prop] = this._getResponseValue(response, prop); 
                    } catch (e){}
                }   
            }
            return true;            
        },
        
        _evalJson: function(jsonString, returnField) {
            var err;
            var msg;
            var response; 
            var result; 
            try {
                result = eval(jsonString);
                if (result.wtResponse !== undefined) {
                    response = result.wtResponse;
                } else {
                    response = result;
                }
            } catch(e) {
                if (returnField === "status") {
                    if (this.msgUnavailable) {
                        this.msg = this.msgUnavailable;
                    }
                    return "offline";
                } else {
                    return;
                }
            } 
            
            /* set message text variables, if present */
            msg = this._getResponseValue(response, "infoMsg");
            if (msg) {
                var aInfoMsg = [];
                aInfoMsg = msg.split("|"); 
                for (var i = 0, len = aInfoMsg.length; i < len; ++i) {
                    if (aInfoMsg[i] !== "") {
                        switch (i) {
                          case 0: this.msgChatBegun = aInfoMsg[i];  break;
                          case 1: this.msgPopUpWarn = aInfoMsg[i];  break;
                        } 
                    }
                }
            } 
            
            /* set error message, if present */
            err = this._getResponseValue(response, "err");
            if (err) {
                msg = (this._getResponseValue(response, "errMsg") || err);
                this.msg = msg;
            } 
            
            /* return the requested field's value */
            return this._getResponseValue(response, returnField); 
        },
        
        /**
         * Retrieve the value of a cookie with the specified name.
         *   
         * @param {String} cName  The name of the requested cookie. 
         *      
         * @return {String} The value of the requested cookie. 
         */
        _getCookie: function (cName) {
          // cookies are separated by semicolons
          
          var aCookie = document.cookie.split("; ");
          for (var i=0; i < aCookie.length; i++)
          {
            // a name/value pair (a crumb) is separated by an equal sign
            var aCrumb = aCookie[i].split("=");
            if (cName == aCrumb[0]) 
              return unescape(aCrumb[1]);
          }
          // a cookie with the requested name does not exist
          return null;
        },

        
        /**
         * Extracts a specific response element's value.  
         *   
         * @param {Object} response  The wtResponse object. 
         * @param {String} tagName  The name of the requested element. 
         *      
         * @return {Object} The reference to the newly created chat window. 
         */
        _getResponseValue: function(response, tagName) {
            var value = "";
            try {
                value = response[tagName];
            } catch(e) {}
            
            return value;
        },
        
        /**
         * jsonp Submits a call using the Cross Domain script tag functionality
         * @url {String} URL to json building server program.
         * @callback {String} name of function for returned script to call to eval JSON.
         * @query {Object} additional query string for URL
         */
        _jsonp: function (url, callback, query) {                
            if (url.indexOf("?") > -1)
                url += "&callback=" 
            else
                url += "?callback=" 
            url += callback + "&";
            if (query)
                url += encodeURIComponent(query) + "&";   
            url += new Date().getTime().toString(); // prevent caching  
            var script = document.createElement("script");        
            script.setAttribute("src",url);
            script.setAttribute("type","text/javascript");                
            document.body.appendChild(script);
        },
         
        /**
         * Opens the chat session window
         *   
         * @param {Object} options The options for the request. 
         * The properties that are recognized are 
         * the same as for _openChatsession.
        */                  
        _openChatInfoForm: function() { 
            var objElem;
            var name;
            var strFeatures
            var strName
            var strUrl
            var objWindow
            var value;
            
            var windowConfig = ( this.startOptions.window || {} );
            
            this.sessionData = {};
            this.sessionData.mode = "edit";
            this.sessionData.chatrequestor = "initiator";
            this.sessionData.cssUrl     = ( windowConfig.cssUrl || "" );
            this.sessionData.footerHtml = ( windowConfig.footerHtml || "" );
            this.sessionData.headerHtml = ( windowConfig.headerHtml || "" );
            this.sessionData.logoUrl    = ( windowConfig.logoUrl || "" );
            this.sessionData.title      = ( windowConfig.title || "" );
            
            strUrl = this.wsUrl
                   +  "webspeed/public/ntOpen.w?" 
                   + "upHtmlFile=webspeed/public/chat/pubChatInfoForm.htm&"
                   + this._urlEncode(this.sessionData); 
            this.chatSessionWindow.location.replace(strUrl);
            
            try {
                if ( !this.chatSessionWindow.closed ) { 
                      this.chatSessionWindow.focus(); 
                } 
            } catch(e) {}                         
        },
            
        /**
         * Opens the chat session window
         *   
         * @param {Object} options The options for the request. 
         * The properties that are recognized are:
         *      
         *   window : Object (Optional)
         *       An object which may contain the following properties:
         *              
         *       top : Number (Optional)
         *             The position of the top edge of the chat window in pixels.
         *       left : Number (Optional)
         *             The position of the left edge of the chat window in pixels.
         *       width : Number (Optional)
         *             The width of the chat window in pixels. 
         *             Default is 360.
         *       height : Number (Optional)
         *             The height of the top of the chat window in pixels. 
         *             Default is 660.
         *       name : String (Optional)
         *             The internal (browser) name of the chat window.
         *       title : String (Optional)
         *             The title of chat window. Default is "CRS Chat Session".
         *       cssUrl : String (Optional)
         *             The fully-qualified URL of an external stylesheet 
         *             to apply to the chat window.
         *       logoUrl : String (Optional)
         *             The fully-qualified URL of an image (e.g. PNG, JPG, JIF) 
         *             to insert at the top of the chat window.
         *       headerHtml : String (Optional)
         *             An HTML fragment to insert at the top of the chat window, 
         *             immediately after the <body> tag.
         *       footerHtml : String (Optional)
         *             An HTML fragment to insert at the bottom of the chat window, 
         *             immediately before the </body> tag. 
         *             Default is none.
         *             
         *   form : Object (Optional)
         *       A reference to a <form> element containing data to be passed 
         *       to the server and added to the contact, e.g. the consumer's name, 
         *       email address, initial question, etc. 
         *       As an alternative (or in addition), specific values may be passed 
         *       to the server using the info property  
         *   info : Object (Optional)
         *       An object which may contain additional property-value pairs 
         *       to be passed to the server and added to the contact, 
         *       e.g. a product / UPC based on the current web page. 
         *       As an alternative (or in addition), the values entered 
         *       into a <form> element may be passed using the form property.
         *   site : String (Optional)
         *       A character string which will be mapped 
         *       to a specific CRS import source table, 
         *       to define processing rules for the sent data.  
         *       Mapped via the CRS config chat/importSource.
         *   clickStream : Array (Optional)
         *       An array of 0..n page IDs to be passed to the server and added 
         *       to the Contact; useful for analyzing how the consumer traverses 
         *       the website before requesting chat.  
         *   shoppingCart : String (Optional)
         *       The ID of an active shopping cart to be passed to the server 
         *       and added to the Contact; useful if the rep will be assisting 
         *       the consumer in purchase decisions, or to tie the chat with a purchase.
         *            
         * @return {Object} The reference to the newly created chat window.  
         */            
        _openChatsession: function() { 
            var objElem;
            var name;
            var strFeatures
            var strName
            var strUrl
            var objWindow
            var value;
            
            var windowConfig = ( this.startOptions.window || {} );
            
            this.sessionData.mode = "edit";
            this.sessionData.chatrequestor = "initiator";
            this.sessionData.cssUrl     = ( windowConfig.cssUrl || "" );
            this.sessionData.footerHtml = ( windowConfig.footerHtml || "" );
            this.sessionData.headerHtml = ( windowConfig.headerHtml || "" );
            this.sessionData.logoUrl    = ( windowConfig.logoUrl || "" );
            this.sessionData.title      = ( windowConfig.title || "" );
            
            strUrl = this.wsUrl
                   +  "webspeed/public/ntOpen.w?" 
                   + "upHtmlFile=webspeed/public/chat/pubChatSession.htm&"
                   + this._urlEncode(this.sessionData);   
            
            this.chatSessionWindow.location.replace(strUrl);
            
            if (this.chatSessionWindow 
            && !this.chatSessionWindow.closed) { 
                try { 
                  this.chatSessionWindow.focus(); 
                } catch(e) {}                 
            }              
        },
        
    
        _postChatsessionComplete: function(jsonString) {
            var err;
            var err;
            var errMsg;
            var msg = "";
            var response; 
            var result;         
            var status;
            
            this.chatStartInProcess = false; 
            try {
                result = eval(jsonString);
                response = result.wtResponse;
            } catch(e) {
                if (this.msgUnavailable) {
                    this.status = "offline";
                    this.msg = this.msgUnavailable;
                    this.statusCallback.call();
                }
                return false;
            }
            
            err = this._getResponseValue(response, "err");
            if (err) {
                msg = (this._getResponseValue(response, "errMsg") || err);
                this.status = "offline";
                this.msg = msg;         
                if (this.statusCallback) {
                    this.statusCallback.call();
                }      
                return false; 
            } 
                    
            //set session params from returned values
            var chatAvailable = this._copyDataFromServer(response); 
            
            if (chatAvailable) {
                this._openChatsession(); 
            }
        },
        
         /**
         *     Sets a browser Cookie
         *     Default expire = when browser session is closed.
         *                       
         * @param {String} cName  The name of the cookie. 
         * @param {String} value  The value of the cookie. 
         * @param {String} expireMinutes  Number of minutes to expire cookie.          
         */
        _setCookie: function (cName, value, expireMinutes) {
            var exdate = new Date();
            exdate.setDate(exdate.getDate() + expireMinutes);
            document.cookie = cName + '=' + escape(value) +
                ';path=/' + 
                ((expireMinutes==null) ? '' : ';expires=' + exdate.toGMTString());
          },
        
        
         /**
         *  Revision of Ext.urlEncode         
         * Takes an object and converts it to an encoded URL. 
         * e.g. urlEncode({foo: 1, bar: 2}); would return "foo=1&bar=2".  
         * @return {String}
         */
        _urlEncode: function(o){
            if(!o){
                return "";
            }
            var buf = [];
            for(var key in o){
                var ov = o[key], k = encodeURIComponent(key);
                var type = typeof ov;
                if(type == 'undefined'){
                    buf.push(k, "=&");
                } else if(type != "function" && type != "object"){
                    buf.push(k, "=", encodeURIComponent(ov), "&");
                } else if(ov.constructor.toString().indexOf("Array") > -1){
                    if (ov.length) {
                        for(var i = 0, len = ov.length; i < len; i++) {
                            buf.push(k, "=", encodeURIComponent(ov[i] === undefined ? '' : ov[i]), "&");
                      }
                    } else {
                        buf.push(k, "=&");
                    }
                }
            }
            buf.pop();
            return buf.join("");
        }
    }
    
    return _singleton;
}();

