﻿function OpenOnlineChat() {
    if (!char_support_status)
        return;
    window.location.href = "/Online/WebChat";
    return false;
}
function OpenCallBack() {

    window.location.href = "/Online/CallBack";
    return false;
}
function OpenProductClass(id) {
    var el = document.getElementById("pclass_" + id);
    if (el.style.display == "") {
        el.style.display = "none";
    } else {
    el.style.display = "";
    }
}
function ShowMoveAbout(){
    var more_about = document.getElementById("more_about");
    var more_about_text = document.getElementById("more_about_text");
    more_about.style.display = "none";
    more_about_text.style.display = "";
 
}

///<reference path="jquery-1.3.2-vsdoc.js">
///<reference path="../js/plugins/input.js">
var sid = "g:32";
var gid = 32;


var TimerAddIMIcon = null;

var FirstMenagerMessage = false;
var StopedChat = false;
var TimeMessagesCount = 0;
var GetMessagesCount = 0;
var GetComposingStateCount = 0;
var contacts_status = 0;
var user;
var chatService;
var messagingForm;
var UserName = "";
var oldUserName = null;
var doCheckStateTimeout;
var oldWinTitle = 'Online chat';
var newMessageTimer;
var isSelectWin = true;
var timeOutInit = -1;
var CharStarted = false;
var CharStartedTime;
var CharStartedError = false;
var OnlineTestStatusTime = 30 * 1000;

function TestIMStatus() {
    var chatService = new IChatService();
    chatService.GetAgentGroupState(gid, OnGetAgentGroupState)
    setTimeout(TestIMStatus, OnlineTestStatusTime);

}
function OnGetAgentGroupState(status) {
    var online_btn = document.getElementById('online_btn');
    var char_support_status = status;
    if (char_support_status == AgentState.Online || char_support_status == AgentState.IdleOnline) {//Online
        //online_btn.className = "online";
        online_btn.src = "/images/online_icon1.png";

    } else {
        //online_btn.className= "online_disable";
        online_btn.src = "/images/online_icon1_d.png";

    }
}

function FunctionAddIMIcon() {
    clearTimeout(TimerAddIMIcon);
    var hrefs = $('span[im_id]');
    var ids = new Array();
    hrefs.each(function (i) {
        var href = $(this).attr("im_id")
        ids.push(href);
    }
    )
    var chatService = new IChatService();
    if (ids.length > 0)
        chatService.GetAgentsState(ids.join(","), OnGetAgentsState)

}
function GetAgentsStateImages(status, sid, show_link, div) {
    var ret = "";
    var link_1 = "";
    var link_2 = "";
    var div_status = $(".sip_status", div);
    var div_text = $(".sip_text", div);
    switch (status) {
        //OffLine=1, Online=2, Buy=3, Unkonw=0, Away=4  
        case 1: //OffLine
            ret = '<img src="' + site_path + 'images/comm/offline1.png" class="icon" width="18" height="16" title="On-line чат"> <span class="chat_status" title="On-line чат">отсутствует</span>';
            break;
        case 2: //Online
            if (show_link)
                link_1 = '<a sid="' + sid + '" href="#" onclick="return OpenIMChat(\'' + sid + '\');" title="On-line чат">';
            ret = '<img src="' + site_path + 'images/comm/available1.png" class="icon" width="18" height="16" title="On-line чат"> <span class="chat_status">доступен</span>';
            if (show_link)
                link_2 = '</a>';
            break;
        case 3: //Busy
            ret = '<img src="' + site_path + 'images/comm/busy1.png" class="icon" width="18" height="16" title="On-line чат"> <span class="chat_status" title="On-line чат">занят</span>';
            break;
        case 0: //Unkonw
            //href.html("<img src='" + site_path + "images/comm/comm04.gif' alt='Не беспокоить' title='Не беспокоить'/> Не беспокоить");
            break;
        case 4: //Away
            if (show_link)
                link_1 = '<a sid="' + sid + '" href="#" onclick="return OpenIMChat(\'' + sid + '\');" title="On-line чат">';
            ret = '<img src="' + site_path + 'images/comm/away1.png" class="icon" width="18" height="16" title="On-line чат"> <span class="chat_status">временно отсутствует</span>';
            if (show_link)
                link_2 = '</a>';
            break;
        default:
            ret = status;
    }
    if (div_status.length > 0) {
        div_status.html(ret);
        div.html(link_1 + '<span class="sip_text">' + div_text.html() + '</span>' + link_2);
    } else {
        div.html(link_1 + ret + link_2);
    }

    return ret;
}
function OnGetAgentsState(result) {
    for (var id in result) {
        var v = result[id];
        if (!v.Key) continue;
        var href = $("span[im_id='" + v.Key + "']");
        GetAgentsStateImages(v.Value, v.Key, true, href);
    }
    TimerAddIMIcon = setTimeout(FunctionAddIMIcon, 25000);
}




function getCurentTime() {

    return '[' + getSimpleCurentTime() + ']';
}
function getSimpleCurentTime() {
    var d = new Date();
    var m = d.getMinutes();
    if (m < 9) m = '0' + m;
    var h = d.getHours();
    if (h < 9) h = '0' + h;
    return h + ':' + m;
}

function scrollToBottom() {
    var dom = document.getElementById('uxConversationHistory');
    dom.scrollTop = (dom.scrollHeight - dom.clientHeight);
}

function doUnRegister() {
    chatService.UnRegister(user);
}

function doRegister() {

    if (chatService == null) chatService = new IChatService();

    var fio = document.getElementById('tbFullName').value;

    if (fio == "") {
        document.getElementById('fio_error').style.dispaly = "";
        return false;
    }


    

    UserName = fio
    document.getElementById('uxConversationHistory').innerHTML = '';
    var uxConversationHistory = document.getElementById('uxConversationHistory');
    uxConversationHistory.innerHTML += '<div class="chat_replica_my"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + (UserName ? UserName : 'Вы') + '</span></div>';
    oldUserName = UserName
    chatService.AgentRegister(fio, "", "", sid, onRegisterSuccess);
    //$("#uxInput").hide();

    AddStatus("Ожидайте присоединения собеседника");
    document.getElementById('start_chat_button').style.dispaly = "none";
    //$("#uxComposingState").html("Ожидайте присоединения собеседника");
    document.getElementById('uxInitState').innerHTML = "Соединение..";

}
function AddStatus(Text) {
    if (Text)
        document.getElementById('chat_status_info').innerHTML = '<span class="chat_ask">' + Text + '</span>';
    else
        document.getElementById('chat_status_info').innerHTML = '&nbsp;';


}
function onRegisterSuccess(result) {

    if (result != null && result != '00000000-0000-0000-0000-000000000000') {
        AddStatus("Ожидайте ответа собеседника");

        user = result;
        //chatService.GetComposingStateCount(user, onGetComposingStateCount);
        setTimeout(doCheckState, 1500);
        document.getElementById('contactForm').style.display = "none";
        document.getElementById('messagingForm').style.display = "";

    } else {
        alert("Пользователь недоступен.");
        document.getElementById('contactForm').style.display = "";
        document.getElementById('messagingForm').style.display = "none";
        document.getElementById('init_input_info').style.display = "";
        document.getElementById('init_input_send').style.display = "";
        document.getElementById('start_chat_button').style.display = "";
    }
}

function doCheckLife() {
    setTimeout(doCheckLife, 10000);
    if (StopedChat) return;
    if ((new Date() - leftPing) > 20000) {
        doCheckState();
    }
}
function doCheckState() {
    try {
        if (StopedChat) return;
        leftPing = new Date();
        clearTimeout(doCheckStateTimeout);
        doCheckStateTimeout = setTimeout(doCheckState, 2500);
        if (GetMessagesCount == 0 || GetMessagesCount > 5) {
            GetMessagesCount = 0;
            chatService.GetMessages(user, onGetMessagesSuccess);
        }
        GetMessagesCount++;

        if (GetComposingStateCount == 0 || GetComposingStateCount > 5) {
            GetComposingStateCount = 0;
        }
        chatService.GetComposingState(user, onGetComposingStateSuccess);
        GetComposingStateCount++;
        TimeMessagesCount++;
        if (TimeMessagesCount > 10) {
            //$('#uxComposingState').html("Проблема с соеденением");
            AddStatus("Проблема с соеденением");
        }

    } catch (e) {
        alert( e.message);
    }
}

function onGetComposingStateCount(result) {
    if (result == 1) {
        document.getElementById('uxInput').style.display = "";
    }
}

function onGetComposingStateSuccess(result) {

    GetComposingStateCount = 0;
    TimeMessagesCount = 0;
    if (result == null) {
        if (FirstMenagerMessage) {
            //$('#uxComposingState').html("&nbsp;");
            AddStatus("");
        }
    }
    else {
        if (result == 'none') {
            result = "Пользователь недоступен";
            if (!CharStarted)
                result = 'Связь прервана, повторите, пожалуйста, попытку соединения';
            StopedChat = true;
            document.getElementById('sendMsgsDiv').style.display = "none";

        }
        //if (result == '') result = "";
        if (FirstMenagerMessage) {
            //$('#uxComposingState').html(result);
            AddStatus(result);

        }
    }
}

function doSendMessage() {

    var input = document.getElementById('uxInput');
    if (oldUserName != UserName) {
        document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_replica_my"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + (UserName ? UserName : 'Вы') + '</span></div>';
    }
    document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_messages">&#155; ' + input.value + "</div>";


    oldUserName = UserName;
    setTimeout(scrollToBottom, 150);
    chatService.SendMessage(user, input.value, onSendMessageSuccess);
    input.value = "";
    doCheckState();
}

function onSendMessageSuccess(result) {

}

function onGetMessagesSuccess(result) {
    try {
        if (!CharStartedError && !CharStarted && (new Date() - CharStartedTime) > 2 * 60000) {
            var text = 'В данный момент никто из сотрудников F1Center не может взять участие в общении, пожалуйста, повторите попытку позже или воспользуйтесь функцией «<a href="./CallBack.aspx">обратный звонок</a>» ';
            document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_warning"><span class="chat_ask">' + text + '</span></div>';
            CharStartedError = true;
        }
        if (timeOutInit > 0) timeOutInit--;
        GetMessagesCount = 0;
        if (result == null) {
            //setTimeout(doCheckState, 1500);
            return;
        }
        if (!FirstMenagerMessage && result.length > 0) {
            //$("#uxInput").show();
            //$("#uxConversationHistory").html("");
            FirstMenagerMessage = true;
        }
        for (var i = 0; i < result.length; i++) {
            if (result[i].Type == "command") {
                DoCommand(result[i].Text.split("|"))
            }
            else {
                if (result[i].Type == "message") {
                    if (oldUserName != result[i].From) {

                        document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_replica"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + result[i].From + '</span></div>';
                    }
                    document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_messages">&#155; ' + result[i].Text + "</div>";
                } else if (result[i].From == "weclom" && timeOutInit <= 0) {
                    document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_' + result[i].From + '"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + result[i].Text + '</span></div>';
                    if (timeOutInit < 0) timeOutInit = 20;
                } else if (result[i].From == "leave" && timeOutInit == 0) {
                    document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_' + result[i].From + '"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + result[i].Text + '</span></div>';
                } else if (result[i].From != "weclom" && result[i].From != "leave") {
                    document.getElementById('uxConversationHistory').innerHTML += '<div class="chat_' + result[i].From + '"><span class="time">' + getCurentTime() + ' </span><span class="chat_ask">' + result[i].Text + '</span></div>';
                }
                oldUserName = result[i].From;
            }
        }
        setTimeout(scrollToBottom, 150);
    } catch (e) {
        alert( e.message);
    }
}
var posNewMessagess = 0;
function DoCommand(vars) {
    var command = vars[1];
    switch (command) {
        case 'inituser':
            var sip = vars[2];
            var name = vars[3];
            var title = vars[4];
            var email = sip.substring(4, sip.length);
            email = email.substring(0, email.indexOf("@"));
            document.getElementById('support_name').innerHTML = name;
            CharStarted = true;
            /*
            $("#chat_avatar").hide();
                     
            for(var s in contact_images){
            if(s.toLowerCase().indexOf(email.toLowerCase())>=0){
            $("#chat_avatar img").attr("src", contact_images[s]);
            $("#chat_avatar").show();
            break;
            }
            }   
            */

            break;
    }
}

function checkStartEnter(e) {
    if (e.keyCode == 13) {
        doRegister();
    }
}

function checkEnter(e) {
    var uxInput = document.getElementById('uxInput')
    if (e.keyCode == 13) {
        if (uxInput.value.length > 0) {
            doSendMessage();
            uxInput.value = '';
        }
        return false;
    }
}
function checkSend() {
    if (uxInput.value.length > 0) {
        doSendMessage();
        uxInput.value = '';
    }
    return false;
}



var IChatService = function () {
    this._timeout = 0;
    this._userContext = null;
    this._succeeded = null;
    this._failed = null;
}
IChatService.prototype = {
    _path: "/Services/ChatService.svc",
    get_path: function () {
        return _path;
    },
    set_path: function () {
        return _path;
    },
    _invoke: function (mthod, is_get, vars, succeededCallback, failedCallback, userContext) {
        var xhr = false;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            try {
                xhr = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) {
                    xhr = false;
                }
            }
        }

        if (xhr) {
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    // and status is "OK"
                    if (xhr.status == 200) {
                        var date = eval("(" + xhr.responseText + ")");

                        succeededCallback(date.d)
                    }
                }
            };
            var date = JSON.stringify(vars);
            /*
            var date = '';
            for (var name in vars) {
            if (date == '') date += "&";
            date += encodeURIComponent(name) + "=" + encodeURIComponent(name);
            }*/
            xhr.open("POST", this._path + "/" + mthod, true);
            xhr.setRequestHeader("Content-Type", "application/json");
            xhr.send(date);
            return true;
        } else {
            return false;
        }
    },
    Register: function (fullName, email, initialMessage, succeededCallback, failedCallback, userContext) {
        return this._invoke('Register', false, { fullName: fullName, email: email, initialMessage: initialMessage }, succeededCallback, failedCallback, userContext);
    },
    AgentRegister: function (fullName, email, initialMessage, ToAgent, succeededCallback, failedCallback, userContext) {
        return this._invoke('AgentRegister', false, { fullName: fullName, email: email, initialMessage: initialMessage, ToAgent: ToAgent }, succeededCallback, failedCallback, userContext);
    },
    SendMessage: function (userId, message, succeededCallback, failedCallback, userContext) {
        return this._invoke('SendMessage', false, { userId: userId, message: message }, succeededCallback, failedCallback, userContext);
    },
    GetMessages: function (userId, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetMessages', false, { userId: userId }, succeededCallback, failedCallback, userContext);
    },
    GetComposingState: function (userId, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetComposingState', false, { userId: userId }, succeededCallback, failedCallback, userContext);
    },
    GetComposingStateCount: function (userId, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetComposingStateCount', false, { userId: userId }, succeededCallback, failedCallback, userContext);
    },
    UnRegister: function (userId, succeededCallback, failedCallback, userContext) {
        return this._invoke('UnRegister', false, { userId: userId }, succeededCallback, failedCallback, userContext);
    },
    GetServiceState: function (succeededCallback, failedCallback, userContext) {
        return this._invoke('GetServiceState', false, {}, succeededCallback, failedCallback, userContext);
    },
    GetAgentState: function (AgentID, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetAgentState', false, { AgentID: AgentID }, succeededCallback, failedCallback, userContext);
    },
    GetAgentsState: function (AgentIDs, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetAgentsState', false, { AgentIDs: AgentIDs }, succeededCallback, failedCallback, userContext);
    },
    GetAgentGroupState: function (GroupID, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetAgentGroupState', false, { GroupID: GroupID }, succeededCallback, failedCallback, userContext);
    },
    GetTrace: function (type, parm, succeededCallback, failedCallback, userContext) {
        return this._invoke('GetTrace', false, { type: type, parm: parm }, succeededCallback, failedCallback, userContext);
    }
}
var AgentState = { Unknown: 0, Offline: 1, Online: 2, Busy: 3, Away: 4 };



if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear() + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate()) + 'T' +
                 f(this.getUTCHours()) + ':' +
                 f(this.getUTCMinutes()) + ':' +
                 f(this.getUTCSeconds()) + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"': '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

        // If the string contains no control characters, no quote characters, and no
        // backslash characters, then we can safely slap some quotes around it.
        // Otherwise we must also replace the offending characters with safe escape
        // sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

        // Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

        // If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

        // If we were called with a replacer function, then call the replacer to
        // obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

        // What happens next depends on the value's type.

        switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

                // JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':

                // If the value is a boolean or null, convert it to a string. Note:
                // typeof null does not produce 'null'. The case is included here in
                // the remote chance that this gets fixed someday.

                return String(value);

                // If the type is 'object', we might be dealing with an object or an array or
                // null.

            case 'object':

                // Due to a specification blunder in ECMAScript, typeof null is 'object',
                // so watch out for that case.

                if (!value) {
                    return 'null';
                }

                // Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

                // Is the value an array?

                if (Object.prototype.toString.apply(value) === '[object Array]') {

                    // The value is an array. Stringify every element. Use null as a placeholder
                    // for non-JSON values.

                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }

                    // Join all of the elements together, separated with commas, and wrap them in
                    // brackets.

                    v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }

                // If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {

                    // Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }

                // Join all of the member texts together, separated with commas,
                // and wrap them in braces.

                v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
        }
    }

    // If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

            // The stringify method takes a value and an optional replacer, and an optional
            // space parameter, and returns a JSON text. The replacer can be a function
            // that can replace values, or an array of strings that will select the keys.
            // A default replacer method can be provided. Use of the space parameter can
            // produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

            // If the space parameter is a number, make an indent string containing that
            // many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

                // If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

            // If there is a replacer, it must be a function or an array.
            // Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

            // Make a fake root object containing our value under the key of ''.
            // Return the result of stringifying the value.

            return str('', { '': value });
        };
    }


    // If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

            // The parse method takes a text and an optional reviver function, and returns
            // a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

                // The walk method is used to recursively walk the resulting structure so
                // that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


            // Parsing happens in four stages. In the first stage, we replace certain
            // Unicode characters with escape sequences. JavaScript handles many characters
            // incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

            // In the second stage, we run the text against regular expressions that look
            // for non-JSON patterns. We are especially concerned with '()' and 'new'
            // because they can cause invocation, and '=' because it can cause mutation.
            // But just to be safe, we want to reject all unexpected forms.

            // We split the second stage into 4 regexp operations in order to work around
            // crippling inefficiencies in IE's and Safari's regexp engines. First we
            // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
            // replace all simple value tokens with ']' characters. Third, we delete all
            // open brackets that follow a colon or comma or that begin the text. Finally,
            // we look to see that the remaining characters are only whitespace or ']' or
            // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

                // In the third stage we use the eval function to compile the text into a
                // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
                // in JavaScript: it can begin a block or an object literal. We wrap the text
                // in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

                // In the optional fourth stage, we recursively walk the new structure, passing
                // each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({ '': j }, '') : j;
            }

            // If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
} ());

