var validation_started = false;

$(document).ready(function() {
	
	$('#search-');
	
  $(".search").focus(function() {
    if($(this).val() == $(this).attr("data-orig-text"))
    {
      $(this).val('');
    } else {
    	$(this).select();
    }
  });
  $(".search").blur(function() {
    if($(this).val() == '')
    {
      $(this).val($(this).attr("data-orig-text"));
    }
  });
  $(".search").each(function() {
    $(this).val($(this).attr("data-orig-text"));
  });
  $(".search").keypress(function(e) {
    if(e.keyCode == 13)
    {
      var doSearch = OpenLayers.Function.bind(map.doSearch,map);
      doSearch();
    }
  });
  $("#change-city").mouseover(function() {
  	$("#city-options").css("visibility","");
  	$(".city").css({"background":"","color":""});
  });
  /*$("#change-city").mouseout(function() {
  	$("#city-options").hide();
  });*/
  var city = $(".current").attr("data-slug");
  if(city != 'new-york') {
		$(".citymaps_now").hide();
	} else {
		$(".citymaps_now").show();
	}
  $(".change-city-link").click(function(e) {
  	if(window.map)
  	{
  		map.clearSearchResults();
  	}
  	var value = $(this).attr("data-slug");
  	var current = $(".current").attr("data-slug");
  	$("#city-options").css("visibility","hidden");
  	//$(".city").css({"background":"#0C8EC8", "color":"#FFFFFF"});
  	if(window.map) {
  		var subways_layer = map.getLayersByName("subways")[0];
	  	if(value != 'new-york') {
	  		$(".citymaps_now").hide();
	  		$(".subway-toggle").hide();
	  	} else {
	  		$(".citymaps_now").show();
	  		$(".subway-toggle").show();
	  	}
  	}
  	//$("#city-options").css("visibility","");
  	if(window.map && value == current)
  	{
  		alert("This is already your selected city.");
  		return;
  	}
  	if($(this).children("a").hasClass("disabled"))
  	{
  		alert("This city is coming soon.");
  		return;
  	}
  	
  	$(".change-city-link").removeClass("current");
  	$(this).addClass("current");
  	var is_map = false;
  	if(window.map)
  	{
  		is_map = true;
  	}
  	$.ajax({
  		url: '/citymaps/updateUserCity?city='+value+"&map="+is_map,
  		dataType: 'json',
  		success: function(data) {
  			if(data.map)
  			{
  				map.city = data.city.slug;
  				map.restrictedExtent = new OpenLayers.Bounds(data.city.top_left.x,data.city.bottom_right.y,data.city.bottom_right.x,data.city.top_left.y);
  				var layer = map.getLayersByName("basemap_logos")[0];
  				map.removeLayer(layer);
  				map.cur_cat_id = 0;
  				cur_cat_id = 0;
  				cur_cat = null;
  				layer.layername = "basemap_logos_0";
  				map.addLayer(layer);
  				map.markersLayer.div.style.zIndex = 2000;
  				$.cookie('city',map.city);
  				$.cookie('city_left',data.city.top_left.x);
  				$.cookie('city_top',data.city.top_left.y);
  				$.cookie('city_right',data.city.bottom_right.x);
  				$.cookie('city_bottom',data.city.bottom_right.y);
  				if(data.user_point != null)
  				{
  					map.setCenter(new OpenLayers.LonLat(parseFloat(data.user_point.x),parseFloat(data.user_point.y)));
  				} else {
  					map.setCenter(new OpenLayers.LonLat(parseFloat(data.city.center.x),parseFloat(data.city.center.y)));
  				}
  				var height = $("#filter-bar").height();
  				$(".sidebar-left").html(data.sidebar_html);
  				$("#filter-bar").height(height);
  				if(data.zoom != null)
  				{
  					map.zoomTo(data.zoom);
  				}
  				initFilterBar();
  				if($("#filter-bar-outer").height() == 0) {
  					toggleFilters(true);
  				}
  				
  				//map.loadUpdates();
  			} else {
  				window.location.href = "/";
  			}
  		}
  	});
  });
});
function setIframeLinks()
{
	$("a").attr("target","_newtab");
}
function mapSearchSubmit() {
  	if($(".search-box-what").val() == $(".search-box-what").attr("data-orig-text"))
  	{
  		$(".search-box-what").val("");
  	}
  	if($(".search-box-where").val() == $(".search-box-where").attr("data-orig-text"))
  	{
  		$(".search-box-where").val("");
  	}
  	return true;
}
function toDollars(val) {
  	var num = '';
  	for(var i=0; i < val.length; i++)
  	{
  		var next = val.charAt(i);
  		if(!isNaN(parseInt(next)) || next == '.')
  		{
  			num += next;
  		}
  	}
  	if(num == '')
  	{
  		val = 0;
  	} else {
  		val = parseFloat(num).toFixed(2);
  	}
  	return val;
}
function isNumeric(val)
{
	return !isNaN(parseFloat(val));
}
function isBoolean(val)
{
	var val = val.toLowerCase();
	return val == "true" || val == "false";
}
function alert(message,title,width,callback)
{
	if(!width)
	{
		width = 275;
	}
	var div_title = "";
	if(title) {
		div_title = ' title="'+title+'"';
	} else {
	}
	//$("body").remove("#dialog_msg");
	if($("#dialog_msg").length == 0)
	{
		$("body").append('<div id="dialog_msg"'+div_title+'></div>');
	}
	var callback_func;
	if(callback) {
		callback_func = function() {
			$(this).dialog("close");
			$("#dialog_msg").remove();
			callback.call();
		}
	} else {
		callback_func = function() {
			$(this).dialog("close");
			$("#dialog_msg").remove();
		}
	}
  $("#dialog_msg").html(message);
  $("#dialog_msg").dialog({
    modal: true,
    buttons: {Ok: callback_func},
	  width: width,
	  dialogClass: 'alert-dialog',
	  resizable: false,
	  movable: false
  });
}
function confirm(message, callback)
{
	if($("#dialog_msg").length == 0)
	{
		$("body").append('<div id="dialog_msg"></div>');
	}
  $("#dialog_msg").html(message);
  $("#dialog_msg").dialog({
    modal: true,
    buttons: {
      Ok: callback,
      Cancel: function() { 
      		$(this).dialog("close"); 
      		$("body").remove("#dialog_msg");
      	}
    }
  });
}
function init_validation()
{
  $("form.validate").each(function() {
    $(this).find(".form-item").each(function() {
      if($(this).siblings(".error").length == 0)
      {
        var id = $(this).attr("id");
        var error_elem = '<div id="'+id+'_error" class="error"></div>';
        $(this).parent().append(error_elem);
      }
    });
  });
}
function form_validate(form,display)
{
  var errors = [];
  form.find('.form-item').each(function() {
    var msg = field_valid($(this));
    var id = $(this).attr("id");
    if(msg != '')
    {
      errors.push({
        id: id,
        msg: msg
      });
    } else {
      var error_elem = get_error_elem(id);
      error_elem.html("");
      error_elem.hide();
    }
  });
  if(display)
  {
    display_errors(errors);
  }
  return errors;
}
function display_errors(errors)
{
  for(var i=0; i < errors.length; i++)
  {
    var error_elem = get_error_elem(errors[i].id);
    error_elem.html(errors[i].msg);
    error_elem.show();
  }
}
function field_valid(field)
{
  var val = field.val();
  var validator_text = field.attr("data-validators");
  var field_validators = parse_field_validators(validator_text);
  var label = get_field_label(field);
  
  for(var fv in field_validators)
  {
    var func = 'field_validate_'+fv;
    var fval = field_validators[fv];
    var message = eval(func+"(val,fval,label)");
    if(message)
    {
      return message;
    }
  }
  return '';
}
function field_validate_required(val,fval,label)
{
  if(fval && $.trim(val) == '')
  {
    return label + ' field is required';
  }
}

function field_validate_email(val,fval,label)
{
  var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
  if(fval && val != '' && !reg.test(val))
  {
    return 'Please enter a valid email address';
  }
}
function field_validate_min_length(val,fval,label)
{
  if(val.length > 1 && val.length < fval)
  {
    return label + ' must be at least '+fval+' characters long.';
  }
}
function field_validate_max_length(val,fval,label)
{
  if(val.length > 1 && val.length > fval)
  {
    return label + ' must not be more than '+fval+' characters long.';
  }
}
function field_validate_numeric(val,fval,label)
{
  if(fval && isNaN(val))
  {
    return label + ' must be a numeric value greater than 0';
  }
}
function field_validate_less_than(val,fval,label)
{
	var test_val = parseFloat(toDollars($("#"+fval).val()));
	val = parseFloat(toDollars(val));
	if(test_val < val)
	{
		var test_label = $("#"+fval).siblings("label").html();
		return label + ' must be less than ' + test_label
	}
	return '';
}
function field_validate_greater_than(val,fval,label)
{
	var test_val = toDollars($("#"+fval).val());
	val = toDollars(val);
	if(test_val > val)
	{
		var test_label = $("#"+fval).siblings("label").html();
		return label + ' must be greater than ' + test_label
	}
	return '';
}
function get_error_elem(id)
{
  return $("#"+id+"_error");
}
function get_field_label(field)
{
  var label = field.siblings("label").html();
  if(label == '')
  {
    label = 'This';
  }
  return label;
}
function parse_field_validators(text)
{
  var field_validators = {};
  var options = text.split(";");
  
  for(var i = 0; i < options.length; i++)
  {
    var kv = options[i].split(":");
    if(kv != '')
    {
      var key = removeSpaces(kv[0]);
      var value = parse_value(removeSpaces(kv[1]));
      if(key != '' && value != '')
      {
        eval('field_validators.'+key+'='+value);
      }
    }
  }
  return field_validators;
}
function removeSpaces(str)
{
	return str.split(" ").join("");
}
function parse_value(value)
{
  if(isBoolean(value) || isNumeric(value))
  {
  	return value;
  }
  return '"'+value+'"';
}
function submit_suggest(id)
{
  var form = $("#submit-suggest"+id).parents("form");
  var errors = form_validate(form,true);
  if(errors.length == 0)
  {
    form.ajaxSubmit({
      success: function(data) {
        if(data.status)
        {
        	$("#email_popup").dialog('close');
          alert("Email Sent.");
          $("#suggest-form").clearForm();
        } else {
          alert("Sending failed.  Please check your recipient's email address and try again.");
        }
      }
    });
  }
  return false;
}
function removeSpotFromBox(bid)
{
	$("#spot_row_"+bid).remove();
	var mini = $("#store_"+bid);
	if(mini.length > 0)
	{
	  if($("#all-spots-table tbody").children().length >= 12)
	  {
	    var link_href = '';
	    var img_src = '';
	    var new_bid = 0;
	    var new_store_name = '';
	    var new_store_title = '';
	    $("#all-spots-table tbody").children().each(function() {
	      var next_bid = $(this).attr("bid");
	      var next_mini = $("#store_"+next_bid);
	      if(next_mini.length == 0)
	      {
	      	var link = $(this).find(".store_link a");
	        link_href = link.attr("href");
	        new_store_title = link.html();
	        if(new_store_name.length > 15)
	        {
	        	new_store_name = new_store_name.substring(0,15) + '&#8230';
	        } else {
	        	new_store_name = new_store_title;
	        }	        
	        
	        img_src = $(this).find(".store_img").attr("src");
	        new_bid = next_bid;
	        return false;
	      }
	    });
	    $("#mini-img-"+bid).attr("src",img_src);
	    $("#store-name-"+bid).html(new_store_name).attr("title",new_store_title).attr("id","store-name-"+new_bid);
	   	$("#store_"+bid+" a").attr("href",link_href);
	    mini.attr("id","store_"+new_bid);
	  } else {
	    mini.remove();
	  }
	}
	var count = parseInt($("#fav-count").html());
	count--;
	$("#fav-count").html(count);
}
function addSpotToBox(data)
{
	var divs = $(".store-outer");
	var html = '';
	var id = '';
	var row_id = '';
	divs.each(function(i, elem) {
		if(i == 0)
		{
			html = $(this).html();
			id = $(this).attr("id");
			row_id = $(this).parent().attr("id");
			$(this).find(".mini-img").attr("src",data.img_src).attr("id","mini-img-"+data.bid);
			$(this).find("a").attr("href",data.url);
			$(this).find(".store-name").html(data.name).attr("id","store-name-"+data.bid);
			$(this).parent().attr("id","store_"+data.bid);
			var row_html = '<tr bid="'+data.bid+'" id="spot_row_"'+data.bid+'>'+
	      '<td><img src="'+data.img_src+'" class="store_img"></td>'+
	      '<td class="all-spots-name">'+
	        '<p class="store_link"><a href="'+data.url+'">'+data.name+'</a></p>'+
	      '</td>'+
	      '<td class="all-spots-options"><a usid="'+data.usid+'" class="remove_spot">Remove Spot</a></td>'+
	    '</tr>';
			$("#all-spots-table tbody").append(row_html);
		} else {
			var tmp = html;
			html = $(this).html();
			$(this).html(tmp);
			
			tmp = id;
			id = $(this).attr("id");
			$(this).attr("id",tmp);
			
			tmp = row_id;
			row_id = $(this).parent().attr("id");
			$(this).parent().attr("id",tmp);
		}
	});
	var count = parseInt($("#fav-count").html());
	count++;
	$("#fav-count").html(count);
}
function validYoutubeUrl(str)
{
	return str.match(/http:\/\/(?:www\.)?youtube.*watch\?v=([a-zA-Z0-9\-_]+)/);
}
function endsWith(str,match)
{
	return (str.match(match+"$")==match);
}
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;
}
$.fn.clearForm = function() {
  return this.each(function() {
     var type = this.type, tag = this.tagName.toLowerCase();
     if (tag == 'form')
       return $(':input',this).clearForm();
     if (type == 'text' || type == 'password' || tag == 'textarea')
       this.value = '';
     else if (type == 'checkbox' || type == 'radio')
       this.checked = false;
     else if (tag == 'select')
       this.selectedIndex = 0;
  });
};

var Base64 = {
	keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
	
	encode: function(input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
		while (i < input.length) {
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}
			output += this.keyStr.charAt(enc1) + this.keyStr.charAt(enc2) + this.keyStr.charAt(enc3) + this.keyStr.charAt(enc4);
		}
		return output;
	},
	
	decode: function(input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
		// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		while (i < input.length) {
			enc1 = this.keyStr.indexOf(input.charAt(i++));
			enc2 = this.keyStr.indexOf(input.charAt(i++));
			enc3 = this.keyStr.indexOf(input.charAt(i++));
			enc4 = this.keyStr.indexOf(input.charAt(i++));
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
			output += String.fromCharCode(chr1);
			if (enc3 != 64) {
				output += String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output += String.fromCharCode(chr3);
			}
		}
		return output;
	} 
};

function strtotime (str, now) {

    var i, match, s, strTmp = '',
        parse = '';

    strTmp = str;
    strTmp = strTmp.replace(/\s{2,}|^\s|\s$/g, ' '); // unecessary spaces
    strTmp = strTmp.replace(/[\t\r\n]/g, ''); // unecessary chars
    if (strTmp == 'now') {
        return (new Date()).getTime() / 1000; // Return seconds, not milli-seconds
    } else if (!isNaN(parse = Date.parse(strTmp))) {
        return (parse / 1000);
    } else if (now) {
        now = new Date(now * 1000); // Accept PHP-style seconds
    } else {
        now = new Date();
    }

    strTmp = strTmp.toLowerCase();

    var __is = {
        day: {
            'sun': 0,
            'mon': 1,
            'tue': 2,
            'wed': 3,
            'thu': 4,
            'fri': 5,
            'sat': 6
        },
        mon: {
            'jan': 0,
            'feb': 1,
            'mar': 2,
            'apr': 3,
            'may': 4,
            'jun': 5,
            'jul': 6,
            'aug': 7,
            'sep': 8,
            'oct': 9,
            'nov': 10,
            'dec': 11
        }
    };

    var process = function (m) {
        var ago = (m[2] && m[2] == 'ago');
        var num = (num = m[0] == 'last' ? -1 : 1) * (ago ? -1 : 1);

        switch (m[0]) {
        case 'last':
        case 'next':
            switch (m[1].substring(0, 3)) {
            case 'yea':
                now.setFullYear(now.getFullYear() + num);
                break;
            case 'mon':
                now.setMonth(now.getMonth() + num);
                break;
            case 'wee':
                now.setDate(now.getDate() + (num * 7));
                break;
            case 'day':
                now.setDate(now.getDate() + num);
                break;
            case 'hou':
                now.setHours(now.getHours() + num);
                break;
            case 'min':
                now.setMinutes(now.getMinutes() + num);
                break;
            case 'sec':
                now.setSeconds(now.getSeconds() + num);
                break;
            default:
                var day;
                if (typeof(day = __is.day[m[1].substring(0, 3)]) != 'undefined') {
                    var diff = day - now.getDay();
                    if (diff == 0) {
                        diff = 7 * num;
                    } else if (diff > 0) {
                        if (m[0] == 'last') {
                            diff -= 7;
                        }
                    } else {
                        if (m[0] == 'next') {
                            diff += 7;
                        }
                    }
                    now.setDate(now.getDate() + diff);
                }
            }
            break;

        default:
            if (/\d+/.test(m[0])) {
                num *= parseInt(m[0], 10);

                switch (m[1].substring(0, 3)) {
                case 'yea':
                    now.setFullYear(now.getFullYear() + num);
                    break;
                case 'mon':
                    now.setMonth(now.getMonth() + num);
                    break;
                case 'wee':
                    now.setDate(now.getDate() + (num * 7));
                    break;
                case 'day':
                    now.setDate(now.getDate() + num);
                    break;
                case 'hou':
                    now.setHours(now.getHours() + num);
                    break;
                case 'min':
                    now.setMinutes(now.getMinutes() + num);
                    break;
                case 'sec':
                    now.setSeconds(now.getSeconds() + num);
                    break;
                }
            } else {
                return false;
            }
            break;
        }
        return true;
    };

    match = strTmp.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);
    if (match != null) {
        if (!match[2]) {
            match[2] = '00:00:00';
        } else if (!match[3]) {
            match[2] += ':00';
        }

        s = match[1].split(/-/g);

        for (i in __is.mon) {
            if (__is.mon[i] == s[1] - 1) {
                s[1] = i;
            }
        }
        s[0] = parseInt(s[0], 10);

        s[0] = (s[0] >= 0 && s[0] <= 69) ? '20' + (s[0] < 10 ? '0' + s[0] : s[0] + '') : (s[0] >= 70 && s[0] <= 99) ? '19' + s[0] : s[0] + '';
        return parseInt(this.strtotime(s[2] + ' ' + s[1] + ' ' + s[0] + ' ' + match[2]) + (match[4] ? match[4] / 1000 : ''), 10);
    }

    var regex = '([+-]?\\d+\\s' + '(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?' + '|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday' + '|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)' + '|(last|next)\\s' + '(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?' + '|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday' + '|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))' + '(\\sago)?';

    match = strTmp.match(new RegExp(regex, 'gi')); // Brett: seems should be case insensitive per docs, so added 'i'
    if (match == null) {
        return false;
    }

    for (i = 0; i < match.length; i++) {
        if (!process(match[i].split(' '))) {
            return false;
        }
    }

    return (now.getTime() / 1000);
}

function date (format, timestamp) {

    var that = this,
        jsdate, f, formatChr = /\\?([a-z])/gi,
        formatChrCb,
        // Keep this here (works, but for code commented-out
        // below for file size reasons)
        //, tal= [],
        _pad = function (n, c) {
            if ((n = n + '').length < c) {
                return new Array((++c) - n.length).join('0') + n;
            }
            return n;
        },
        txt_words = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    formatChrCb = function (t, s) {
        return f[t] ? f[t]() : s;
    };
    f = {
        // Day
        d: function () { // Day of month w/leading 0; 01..31
            return _pad(f.j(), 2);
        },
        D: function () { // Shorthand day name; Mon...Sun
            return f.l().slice(0, 3);
        },
        j: function () { // Day of month; 1..31
            return jsdate.getDate();
        },
        l: function () { // Full day name; Monday...Sunday
            return txt_words[f.w()] + 'day';
        },
        N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun]
            return f.w() || 7;
        },
        S: function () { // Ordinal suffix for day of month; st, nd, rd, th
            var j = f.j();
            return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
        },
        w: function () { // Day of week; 0[Sun]..6[Sat]
            return jsdate.getDay();
        },
        z: function () { // Day of year; 0..365
            var a = new Date(f.Y(), f.n() - 1, f.j()),
                b = new Date(f.Y(), 0, 1);
            return Math.round((a - b) / 864e5) + 1;
        },

        // Week
        W: function () { // ISO-8601 week number
            var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3),
                b = new Date(a.getFullYear(), 0, 4);
            return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
        },

        // Month
        F: function () { // Full month name; January...December
            return txt_words[6 + f.n()];
        },
        m: function () { // Month w/leading 0; 01...12
            return _pad(f.n(), 2);
        },
        M: function () { // Shorthand month name; Jan...Dec
            return f.F().slice(0, 3);
        },
        n: function () { // Month; 1...12
            return jsdate.getMonth() + 1;
        },
        t: function () { // Days in month; 28...31
            return (new Date(f.Y(), f.n(), 0)).getDate();
        },

        // Year
        L: function () { // Is leap year?; 0 or 1
            return new Date(f.Y(), 1, 29).getMonth() === 1 | 0;
        },
        o: function () { // ISO-8601 year
            var n = f.n(),
                W = f.W(),
                Y = f.Y();
            return Y + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
        },
        Y: function () { // Full year; e.g. 1980...2010
            return jsdate.getFullYear();
        },
        y: function () { // Last two digits of year; 00...99
            return (f.Y() + "").slice(-2);
        },

        // Time
        a: function () { // am or pm
            return jsdate.getHours() > 11 ? "pm" : "am";
        },
        A: function () { // AM or PM
            return f.a().toUpperCase();
        },
        B: function () { // Swatch Internet time; 000..999
            var H = jsdate.getUTCHours() * 36e2,
                // Hours
                i = jsdate.getUTCMinutes() * 60,
                // Minutes
                s = jsdate.getUTCSeconds(); // Seconds
            return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
        },
        g: function () { // 12-Hours; 1..12
            return f.G() % 12 || 12;
        },
        G: function () { // 24-Hours; 0..23
            return jsdate.getHours();
        },
        h: function () { // 12-Hours w/leading 0; 01..12
            return _pad(f.g(), 2);
        },
        H: function () { // 24-Hours w/leading 0; 00..23
            return _pad(f.G(), 2);
        },
        i: function () { // Minutes w/leading 0; 00..59
            return _pad(jsdate.getMinutes(), 2);
        },
        s: function () { // Seconds w/leading 0; 00..59
            return _pad(jsdate.getSeconds(), 2);
        },
        u: function () { // Microseconds; 000000-999000
            return _pad(jsdate.getMilliseconds() * 1000, 6);
        },

        // Timezone
        e: function () { // Timezone identifier; e.g. Atlantic/Azores, ...
            // The following works, but requires inclusion of the very large
            // timezone_abbreviations_list() function.
/*              return this.date_default_timezone_get();
*/
            throw 'Not supported (see source code of date() for timezone on how to add support)';
        },
        I: function () { // DST observed?; 0 or 1
            // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
            // If they are not equal, then DST is observed.
            var a = new Date(f.Y(), 0),
                // Jan 1
                c = Date.UTC(f.Y(), 0),
                // Jan 1 UTC
                b = new Date(f.Y(), 6),
                // Jul 1
                d = Date.UTC(f.Y(), 6); // Jul 1 UTC
            return 0 + ((a - c) !== (b - d));
        },
        O: function () { // Difference to GMT in hour format; e.g. +0200
            var tzo = jsdate.getTimezoneOffset(),
                a = Math.abs(tzo);
            return (tzo > 0 ? "-" : "+") + _pad(Math.floor(a / 60) * 100 + a % 60, 4);
        },
        P: function () { // Difference to GMT w/colon; e.g. +02:00
            var O = f.O();
            return (O.substr(0, 3) + ":" + O.substr(3, 2));
        },
        T: function () { // Timezone abbreviation; e.g. EST, MDT, ...
            // The following works, but requires inclusion of the very
            // large timezone_abbreviations_list() function.
/*              var abbr = '', i = 0, os = 0, default = 0;
            if (!tal.length) {
                tal = that.timezone_abbreviations_list();
            }
            if (that.php_js && that.php_js.default_timezone) {
                default = that.php_js.default_timezone;
                for (abbr in tal) {
                    for (i=0; i < tal[abbr].length; i++) {
                        if (tal[abbr][i].timezone_id === default) {
                            return abbr.toUpperCase();
                        }
                    }
                }
            }
            for (abbr in tal) {
                for (i = 0; i < tal[abbr].length; i++) {
                    os = -jsdate.getTimezoneOffset() * 60;
                    if (tal[abbr][i].offset === os) {
                        return abbr.toUpperCase();
                    }
                }
            }
*/
            return 'UTC';
        },
        Z: function () { // Timezone offset in seconds (-43200...50400)
            return -jsdate.getTimezoneOffset() * 60;
        },

        // Full Date/Time
        c: function () { // ISO-8601 date.
            return 'Y-m-d\\Th:i:sP'.replace(formatChr, formatChrCb);
        },
        r: function () { // RFC 2822
            return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
        },
        U: function () { // Seconds since UNIX epoch
            return jsdate.getTime() / 1000 | 0;
        }
    };
    this.date = function (format, timestamp) {
        that = this;
        jsdate = ((typeof timestamp === 'undefined') ? new Date() : // Not provided
        (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
        new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
        );
        return format.replace(formatChr, formatChrCb);
    };
    return this.date(format, timestamp);
}

function strip_tags (input, allowed) {
    allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
        commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
    return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
        return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
    });
}

