var COOKIE_NAME = 'ir_cookie2';
var options = { path: '/', expires: 10 };

jQuery(document).ready(function($) {

    var aCookie   = $.cookies.get(COOKIE_NAME);
    if (aCookie == null) aCookie = new Array();

    $('.facebook').html('<img src="/public/images/facebook.png">');

    $("#frmSignup input").focus(function() {
        if ($(this).val() == '' || $(this).val() == 'Your name' || $(this).val() == 'Your email'
                    || $(this).val() == 'Tu Nombre' || $(this).val() == 'Tu Correo electrónico'
        ) {
            $(this).val('');
        }
    });

    $("#frmSignup").submit(function() {

        var s = true;
        $("#frmSignup input").each(function(index) {
            if ($(this).val() == '' || $(this).val() == 'Your name' || $(this).val() == 'Your email'
                        || $(this).val() == 'Tu Nombre' || $(this).val() == 'Tu Correo electrónico'
            ) {
                alert('All fields are required. Please enter a value.');
                s = false;
                return;
            }
        });
        
        var dataString = 'name='+ $("#frmSignup input[name=frmName]").val() + '&email=' + $("#frmSignup input[name=frmEmail]").val()+ '&lang=' + $("#frmSignup input[name=lang]").val();
        $.ajax({  
          type: "POST",  
          url: "/save_news.php", 
          data: dataString,  
          success: function() {  
            $('#frmSignup').html("<div id='messagenews'>Contact Form Submitted!</div>");  
          }
      });
        
        
        
/*        if (s) {
            if (!isValidEmailAddress($("#frmSignup .frmEmail").val())) {
                alert('Your email is invalid');
                s = false;
            }
        }*/
        return false;
    });

    var h = $(window).height() - 62;
    $("#ir, #irh, #irs, #ird").height(h);
	$("div.scrollable").height(h);
	$("div.scrollable div.items div").height(h);
    // $("div.scrollable").height(800);
    // $("div.scrollable div.items div").each(function(index) {        
    //     if ($(this).height()<h) $(this).height(800);
    // });

    $(window).bind('resize', function() { 
        var h = $(window).height() - 62;
        $("#ir, #irh, #irs, #ird").height(h);
    });


    $('#departure_from').attr("disabled","disabled");

	$("#checked_depature").click(function()				
	{
		var checked_status = this.checked;
		if (checked_status) {
            $('#departure_from').attr("disabled","");
		} else {
		    $('#departure_from').attr("disabled","disabled");

		}
	});

    var today = new Date();
    var start = new Date(2010,4,5);
    if (today<start) {
        today = start;
    }
    var in_a_week   = new Date();
    in_a_week.setDate(today.getDate()+7);
    

	$("#arrival_f").datepicker({
	        showOn: 'both', 
	        buttonImage: URL_PUBLIC+'public/themes/ibizarock/images/calendar.gif', 
            defaultDate:new Date(2010,4,5),
            dateFormat: 'dd/mm/yy',
            buttonImageOnly: true, 
	        onSelect: function(dateText, inst) { 
               $("#departure_f").datepicker('option', 'defaultDate', $("#arrival_f").datepicker( 'getDate' )+7);
               var d = $("#arrival_f").datepicker( 'getDate' );
               d.setDate(d.getDate()+7);
               $("#departure_f").val(d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear());               
	         }
	}).val(today.getDate() + '/' + (today.getMonth() + 1) + '/' + today.getFullYear());
	
	
	
	$("#departure_f").datepicker({
	    showOn: 'both', 
	    buttonImage: URL_PUBLIC+'public/themes/ibizarock/images/calendar.gif', 
        defaultDate:new Date(2010,4,5),
        dateFormat: 'dd/mm/yy',
        buttonImageOnly: true
    }).val(in_a_week.getDate() + '/' + (today.getMonth() + 1) + '/' + today.getFullYear());
	
	
	$("#Ibzform").submit(function() {
	    if (!$('#departure_from').attr("disabled")) {
	        var d = $("#arrival_f").val().split('/');
	        var a = $("#departure_f").val().split('/');

	        
	        if ($('.language').val() == 'es') {            
	            var u = 'http://www.skyscanner.net/vuelos/'+$('#departure_from2').val()+'/pmi/10'+padout(d[1])+padout(d[0])+'/10'+padout(a[1])+padout(a[0])+'/tarifas-de-'+$("#departure_from2 option:selected").text()+'-a-ibiza.html';
	        } else {
    	        var u = 'http://www.skyscanner.net/flights/'+$('#departure_from').val()+'/ibz/10'+padout(d[1])+padout(d[0])+'/10'+padout(a[1])+padout(a[0])+'/airfares-to-ibiza-from-'+$("#departure_from option:selected").text()+'.html';
	        }

	        window.open(u);
	    }
	});
	
	
		
    $('#departure_from2').attr("disabled","disabled");

	$("#checked_depature2").click(function()				
	{
		var checked_status = this.checked;
		if (checked_status) {
            $('#departure_from2').attr("disabled","");
		} else {
		    $('#departure_from2').attr("disabled","disabled");

		}
	});
	
    var today = new Date();
    var start = new Date(2010,4,1);
    if (today<start) {
        today = start;
    }
    var in_a_week   = new Date();
    in_a_week.setDate(today.getDate()+7);

	$("#arrival_f2").datepicker({
	        showOn: 'both', 
	        buttonImage: URL_PUBLIC+'public/themes/ibizarock/images/calendar.gif', 
            defaultDate:new Date(2010,4,1),
            dateFormat: 'dd/mm/yy',
            buttonImageOnly: true, 
	        onSelect: function(dateText, inst) { 
               $("#departure_f2").datepicker('option', 'defaultDate', $("#arrival_f2").datepicker( 'getDate' ));
               var d = $("#arrival_f2").datepicker( 'getDate' );
               d.setDate(d.getDate()+7);
               $("#departure_f2").val(d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear());               
	         }
	}).val(today.getDate() + '/' + (today.getMonth() + 1) + '/' + today.getFullYear());


	$("#departure_f2").datepicker({
	    showOn: 'both', 
	    buttonImage: URL_PUBLIC+'public/themes/ibizarock/images/calendar.gif', 
        defaultDate:new Date(2010,4,1),
        dateFormat: 'dd/mm/yy',
        	    buttonImageOnly: true
    }).val(in_a_week.getDate() + '/' + (today.getMonth() + 1) + '/' + today.getFullYear());
	
	
	$("#Ibzform2").submit(function() {

	    if (!$('#departure_from2').attr("disabled")) {
	        var d = $("#arrival_f2").val().split('/');
	        var a = $("#departure_f2").val().split('/');
	        
	        if ($('.language').val() == 'es') {            
	            var u = 'http://www.skyscanner.net/vuelos/'+$('#departure_from2').val()+'/pmi/10'+padout(d[1])+padout(d[0])+'/10'+padout(a[1])+padout(a[0])+'/tarifas-de-'+$("#departure_from2 option:selected").text()+'-a-mallorca.html';
	        } else {
	            var u = 'http://www.skyscanner.net/flights/'+$('#departure_from2').val()+'/pmi/10'+padout(d[1])+padout(d[0])+'/10'+padout(a[1])+padout(a[0])+'/airfares-to-mallorca-from-'+$("#departure_from2 option:selected").text()+'.html';
	        }
	        

	        window.open(u);
	    }
	});
/*
	$(".wrapper").scrollable({
	    size:1	
	}).autoscroll(10000)
	.navigator({});
*/	
	var u = window.location.pathname.split('/')
    if(u[u.length-1] == 'flight-search.html') {
        $("#checked_depature").click();
        $('#departure_from').attr("disabled","");
        $("#checked_depature2").click();
        $('#departure_from2').attr("disabled","");
    }

//    $('.items').animate({left: -150}, 400, 'swing');					
	
    try{
         if (imgP != undefined) {
            var t = setInterval('immm()', 5000);
            var curr = 0;

            immm = function()  {
                var im = imgP[curr];
                if (im == '') {
                    curr = 0
                    im = imgP[curr];                        
                }
                $("#slideshow").attr('src',im);
                curr++;
                if (curr == imgP.length) curr = 0;
            }
            immm();            
        }
    }catch(e){
    }

    if ($("#ir").size()>0) {
        $('#liEvents').removeClass('off').addClass('on');
        $('#liEventos').removeClass('off').addClass('on');
    }

    $('.book').click(function() {
        $(this).parent().find('input').each(function(index) {
            $("#"+$(this).attr('name')+"_f").val($(this).val());
        });
        $("#Ibzform").submit();
    });


if ($('table.cart').size() > 0) {
    var ids = [];
    var nbs = [];    
    for(var i in aCookie) {
        ids.push(aCookie[i].id);
        nbs.push(aCookie[i].nb);
    }
    if (ids.length == 0)  window.location = "../info/";
    
    var i = $('table.cart').attr('id');
    
    $.get(URL_PUBLIC + 'event_manager/cart/', {'ids[]':ids,'nbs[]':nbs,'hotel':i}, function(data){
        $('table.cart tr:last').after(data);
    });
    
    
    $('.qqt_minus').live('click', function(){
        if ($(this).next().val() >= 1) {
		    $(this).next().val(Number($(this).next().val())-1);            
    		var id = $(this).prev().attr('id').replace('qqt_', '');
            for(var i in aCookie) {
                if (aCookie[i].id == id) {
                    aCookie[i].nb = $(this).prev().val();
                }
            }
            $.cookies.set(COOKIE_NAME,aCookie, options);

        }

    });
    $('.qqt_plus').live('click', function(){
		$(this).prev().val(Number($(this).prev().val())+1);
		
		var id = $(this).prev().attr('id').replace('qqt_', '');
        for(var i in aCookie) {
            if (aCookie[i].id == id) {
                aCookie[i].nb = $(this).prev().val();
            }
        }
            $.cookies.set(COOKIE_NAME,aCookie, options);		
    });
    $('.qqt_del').live('click', function(){
        $(this).parent().parent("tr:first").find('td').each(function(){
            if(!$(this).hasClass('tdform')) {
                $(this).html('');
            }
        });
    });
    
    $("#Menu1_SearchBtnFrm").live("click",function(){
        $('#cartform').submit();
    })
    
    $('#cartform').live("submit", function(){
        
        var ids = [];
        var nbs = [];
        var user = [];
        
        if ($('#firstname').val() == '') {
            alert('Please fill up your firstname');
            return false;
        }
        if ($('#lastname').val() == '') {
            alert('Please fill up your lastname');
            return false;
        }
        if ($('#email').val() == '') {
            alert('Please fill up your email');
            return false;
        }
        if ($('#booking').val() == '') {
            alert('Please fill up your booking reference');
            return false;
        }
        
        
        
        user.push($('#firstname').val());
        user.push($('#lastname').val());
        user.push($('#email').val());
        user.push($('#booking').val());

        for(var i in aCookie) {
            ids.push(aCookie[i].id);
            nbs.push(aCookie[i].nb);
        }
        $.get(URL_PUBLIC + 'event_manager/mail/', {'ids[]':ids,'nbs[]':nbs,'user[]':user, 'hotel':$('#hotel').val()}, function(data){
            alert('Thank you, a confirmation email has be sent to the email address provided.');
            $.cookies.del(COOKIE_NAME);
            window.location = "../info/";
        });
        return false;

    });
    
    
    
}



if ($('#calendar').size() > 0) {
    
    
    
    $('.addCart').live('click', function(){
        var nb  = $(this).prev().find('input').val();
        var id  = $(this).attr('id').replace('cart_', '');
        aCookie.push({id:id,nb:nb});
        $.cookies.set(COOKIE_NAME,aCookie, options);
        $(this).parent().find('.added').show('slow');
    });
    $('.cart_view').live('click', function(){
        window.location = 'cart.html';
    });

    
    
    $('.navCalendar input').change(function(){

        var aIds = $(this).attr("value").split(',');        
        for (var i in aIds) {
            $(":input[value="+aIds[i]+"]").attr('checked',$(this).is(":checked"));
        }

        if (!$(':input[value="20"]').is(":checked")) {
            $(".calendar-20").hide();
        }

        $('.navCalendar input').each(function(){
            if ($(this).val() < 20) {
                if ($(this).is(":checked")) {
                    $(".calendar-"+$(this).attr('value')).show();          
                } else {
                    $(".calendar-"+$(this).attr('value')).hide();
                }
            }
        });
        if ($(':input[value="20"]').is(":checked")) {
            $(".calendar-20").show();
        }
    });

    $.fn.qtip.styles.mystyle = { // Last part is the name of the style
       width: 300,

       background: '#8334FA',
       color: 'black',
       border: {
          width: 7,
          radius: 5,
          color: '#8334FA'
       },
       tip: 'leftMiddle',
       name: 'dark' // Inherit the rest of the attributes from the preset dark style
    }
    
    $('#calendar').fullCalendar({
        editable: false,
    	defaultView:'basicWeek',        
        header: {
    		left: '',
    		center: '',
    		right: 'prev month title basicWeek next'
    	},
    	weekMode:'liquid',
    	buttonText: {
    		prev: '&nbsp;<img src="'+URL_PUBLIC+'public/themes/ibizarock/images/cal_left.png">&nbsp;',
    		next: '&nbsp;<img src="'+URL_PUBLIC+'public/themes/ibizarock/images/cal_right.png">&nbsp;'
        },
        aspectRatio: 1.35,    	
        columnFormat: {
    		month: 'dddd d',
    		week: 'ddd M/d',
    		day: 'dddd M/d'
    	},
        eventMouseover: function(calEvent, jsEvent, view) {
            var p = 'leftMiddle';
            if($(this).position().left > 500) { p = 'rightMiddle'; }

            if ($(this).data('qtip') == 1) return;

            $(this).qtip({
               content: 'This is an active list element',
               show: { ready: true },
               hide: { when: 'mouseout', fixed: true },
               target: p,               
               style: { // Last part is the name of the style
                  width: 300,

                  background: '#8334FA',
                  color: 'black',
                  border: {
                     width: 7,
                     radius: 5,
                     color: '#8334FA'
                  },
                  tip: p,
                  name: 'dark' // Inherit the rest of the attributes from the preset dark style
               },
               position: {
                  corner: {
                     target: 'center',
                     tooltip: p
                  }
               },
               content: {
                  url: URL_PUBLIC + 'event_manager/get/',
                  data: { id: calEvent.id }
               }
            });

            $(this).data('qtip', 1);

        },

    	events:URL_PUBLIC + 'event_manager/feed/?hotel='+$("#hotel_id").val(),
    	loading:function(isLoading, view ) {

            if (!isLoading) {

        	    var vars = getUrlVars();
        	    
        	    if (vars['hotel']) {
        	        $('.navCalendar input').attr('checked', false);
        	        $('.navCalendar input:gt(1):lt(4)').attr('checked', true);
        	    }

        	    if (vars['club']) {
        	        $('.navCalendar input').attr('checked', false);
        	        if ($("#hotel_id").val() == 2) {
        	            $('.navCalendar input:gt(5):lt(9)').attr('checked', true);        	            
        	        } else {
        	            $('.navCalendar input:gt(5):lt(7)').attr('checked', true);        	            
        	        }
                    $(':input[value="20"]').attr('checked', true);        	            
        	    }
        	    
        	    if (vars['recommends']) {
        	        $('.navCalendar input').attr('checked', false);
        	        $('.navCalendar input[value="20"]').attr('checked', true);
        	    }

        	    $(".fc-event").hide();
                $('.navCalendar input').each(function(){

                    if ($(this).is(":checked")) {
                        $(".calendar-"+$(this).val()).show();          
                    } else {
                        $(".calendar-"+$(this).val()).hide();
                    }

                });

                if ($(':input[value="20"]').is(":checked")) {
                     $(".calendar-20").show();
                 }
                
            }
    	}
        
    });

}				
});

function padout(number) { return (number < 10 && number.length<2) ? '0' + number : number; }
function getUrlVars()
{
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

function isValidEmailAddress(emailAddress) {
    var pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
    return pattern.test(emailAddress);
}


var jaaulde=window.jaaulde||{};jaaulde.utils=jaaulde.utils||{};jaaulde.utils.cookies=(function(){var resolveOptions,assembleOptionsString,parseCookies,constructor,defaultOptions={expiresAt:null,path:'/',domain:null,secure:false};resolveOptions=function(options){var returnValue,expireDate;if(typeof options!=='object'||options===null){returnValue=defaultOptions;}else
{returnValue={expiresAt:defaultOptions.expiresAt,path:defaultOptions.path,domain:defaultOptions.domain,secure:defaultOptions.secure};if(typeof options.expiresAt==='object'&&options.expiresAt instanceof Date){returnValue.expiresAt=options.expiresAt;}else if(typeof options.hoursToLive==='number'&&options.hoursToLive!==0){expireDate=new Date();expireDate.setTime(expireDate.getTime()+(options.hoursToLive*60*60*1000));returnValue.expiresAt=expireDate;}if(typeof options.path==='string'&&options.path!==''){returnValue.path=options.path;}if(typeof options.domain==='string'&&options.domain!==''){returnValue.domain=options.domain;}if(options.secure===true){returnValue.secure=options.secure;}}return returnValue;};assembleOptionsString=function(options){options=resolveOptions(options);return((typeof options.expiresAt==='object'&&options.expiresAt instanceof Date?'; expires='+options.expiresAt.toGMTString():'')+'; path='+options.path+(typeof options.domain==='string'?'; domain='+options.domain:'')+(options.secure===true?'; secure':''));};parseCookies=function(){var cookies={},i,pair,name,value,separated=document.cookie.split(';'),unparsedValue;for(i=0;i<separated.length;i=i+1){pair=separated[i].split('=');name=pair[0].replace(/^\s*/,'').replace(/\s*$/,'');try
{value=decodeURIComponent(pair[1]);}catch(e1){value=pair[1];}if(typeof JSON==='object'&&JSON!==null&&typeof JSON.parse==='function'){try
{unparsedValue=value;value=JSON.parse(value);}catch(e2){value=unparsedValue;}}cookies[name]=value;}return cookies;};constructor=function(){};constructor.prototype.get=function(cookieName){var returnValue,item,cookies=parseCookies();if(typeof cookieName==='string'){returnValue=(typeof cookies[cookieName]!=='undefined')?cookies[cookieName]:null;}else if(typeof cookieName==='object'&&cookieName!==null){returnValue={};for(item in cookieName){if(typeof cookies[cookieName[item]]!=='undefined'){returnValue[cookieName[item]]=cookies[cookieName[item]];}else
{returnValue[cookieName[item]]=null;}}}else
{returnValue=cookies;}return returnValue;};constructor.prototype.filter=function(cookieNameRegExp){var cookieName,returnValue={},cookies=parseCookies();if(typeof cookieNameRegExp==='string'){cookieNameRegExp=new RegExp(cookieNameRegExp);}for(cookieName in cookies){if(cookieName.match(cookieNameRegExp)){returnValue[cookieName]=cookies[cookieName];}}return returnValue;};constructor.prototype.set=function(cookieName,value,options){if(typeof options!=='object'||options===null){options={};}if(typeof value==='undefined'||value===null){value='';options.hoursToLive=-8760;}else if(typeof value!=='string'){if(typeof JSON==='object'&&JSON!==null&&typeof JSON.stringify==='function'){value=JSON.stringify(value);}else
{throw new Error('cookies.set() received non-string value and could not serialize.');}}var optionsString=assembleOptionsString(options);document.cookie=cookieName+'='+encodeURIComponent(value)+optionsString;};constructor.prototype.del=function(cookieName,options){var allCookies={},name;if(typeof options!=='object'||options===null){options={};}if(typeof cookieName==='boolean'&&cookieName===true){allCookies=this.get();}else if(typeof cookieName==='string'){allCookies[cookieName]=true;}for(name in allCookies){if(typeof name==='string'&&name!==''){this.set(name,null,options);}}};constructor.prototype.test=function(){var returnValue=false,testName='cT',testValue='data';this.set(testName,testValue);if(this.get(testName)===testValue){this.del(testName);returnValue=true;}return returnValue;};constructor.prototype.setOptions=function(options){if(typeof options!=='object'){options=null;}defaultOptions=resolveOptions(options);};return new constructor();})();(function(){if(window.jQuery){(function($){$.cookies=jaaulde.utils.cookies;var extensions={cookify:function(options){return this.each(function(){var i,nameAttrs=['name','id'],name,$this=$(this),value;for(i in nameAttrs){if(!isNaN(i)){name=$this.attr(nameAttrs[i]);if(typeof name==='string'&&name!==''){if($this.is(':checkbox, :radio')){if($this.attr('checked')){value=$this.val();}}else if($this.is(':input')){value=$this.val();}else
{value=$this.html();}if(typeof value!=='string'||value===''){value=null;}$.cookies.set(name,value,options);break;}}}});},cookieFill:function(){return this.each(function(){var n,getN,nameAttrs=['name','id'],name,$this=$(this),value;getN=function(){n=nameAttrs.pop();return!!n;};while(getN()){name=$this.attr(n);if(typeof name==='string'&&name!==''){value=$.cookies.get(name);if(value!==null){if($this.is(':checkbox, :radio')){if($this.val()===value){$this.attr('checked','checked');}else
{$this.removeAttr('checked');}}else if($this.is(':input')){$this.val(value);}else
{$this.html(value);}}break;}}});},cookieBind:function(options){return this.each(function(){var $this=$(this);$this.cookieFill().change(function(){$this.cookify(options);});});}};$.each(extensions,function(i){$.fn[i]=this;});})(window.jQuery);}})();


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');
        };
    }
}());
