(function () {
	var $D = Date;
	$D.Grammar = {};
	var _ = $D.Parsing.Operators, g = $D.Grammar, t = $D.Translator, _fn;
	// Allow rolling up into general purpose rules
	_fn = function () {
		return _.each(_.any.apply(null, arguments), _.not(g.ctoken2("timeContext")));
	};
	
	g.datePartDelimiter = _.rtoken(/^([\s\-\.\,\/\x27]+)/);
	g.timePartDelimiter = _.stoken(":");
	g.whiteSpace = _.rtoken(/^\s*/);
	g.generalDelimiter = _.rtoken(/^(([\s\,]|at|@|on)+)/);
  
	var _C = {};
	g.ctoken = function (keys) {
		var fn = _C[keys];
		if (! fn) {
			var c = Date.CultureInfo.regexPatterns;
			var kx = keys.split(/\s+/), px = [];
			for (var i = 0; i < kx.length ; i++) {
				px.push(_.replace(_.rtoken(c[kx[i]]), kx[i]));
			}
			fn = _C[keys] = _.any.apply(null, px);
		}
		return fn;
	};
	g.ctoken2 = function (key) {
		return _.rtoken(Date.CultureInfo.regexPatterns[key]);
	};
	var cacheProcessRtoken = function (key, token, type, eachToken) {
		if (eachToken) {
			g[key] = _.cache(_.process(_.each(_.rtoken(token),_.optional(g.ctoken2(eachToken))), type));
		} else {
			g[key] = _.cache(_.process(_.rtoken(token), type));
		}
	};
	var cacheProcessCtoken = function (token, type) {
		return _.cache(_.process(g.ctoken2(token), type));
	};
	var _F = {}; //function cache

	var _get = function (f) {
		_F[f] = (_F[f] || g.format(f)[0]);
		return _F[f];
	};

	g.allformats = function (fx) {
		var rx = [];
		if (fx instanceof Array) {
			for (var i = 0; i < fx.length; i++) {
				rx.push(_get(fx[i]));
			}
		} else {
			rx.push(_get(fx));
		}
		return rx;
	};
  
	g.formats = function (fx) {
		if (fx instanceof Array) {
			var rx = [];
			for (var i = 0 ; i < fx.length ; i++) {
				rx.push(_get(fx[i]));
			}
			return _.any.apply(null, rx);
		} else {
			return _get(fx);
		}
	};

	var grammarFormats = {
		 timeFormats: function(){
			var i,
			RTokenKeys = [
				"h",
				"hh",
				"H",
				"HH",
				"m",
				"mm",
				"s",
				"ss",
				"ss.s",
				"z",
				"zz"
			],
			RToken = [
				/^(0[0-9]|1[0-2]|[1-9])/,
				/^(0[0-9]|1[0-2])/,
				/^([0-1][0-9]|2[0-3]|[0-9])/,
				/^([0-1][0-9]|2[0-3])/,
				/^([0-5][0-9]|[0-9])/,
				/^[0-5][0-9]/,
				/^([0-5][0-9]|[0-9])/,
				/^[0-5][0-9]/,
				/^[0-5][0-9]\.[0-9]{1,3}/,
				/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/,
				/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/
			],
			tokens = [
				t.hour,
				t.hour,
				t.hour,
				t.minute,
				t.minute,
				t.second,
				t.second,
				t.secondAndMillisecond,
				t.timezone,
				t.timezone,
				t.timezone
			];

			for (i=0; i < RTokenKeys.length; i++) {
				cacheProcessRtoken(RTokenKeys[i], RToken[i], tokens[i]);
			}

			g.hms = _.cache(_.sequence([g.H, g.m, g.s], g.timePartDelimiter));

			g.t = cacheProcessCtoken("shortMeridian", t.meridian);
			g.tt = cacheProcessCtoken("longMeridian", t.meridian);
			g.zzz = cacheProcessCtoken("timezone", t.timezone);

			g.timeSuffix = _.each(_.ignore(g.whiteSpace), _.set([ g.tt, g.zzz ]));
			g.time = _.each(_.optional(_.ignore(_.stoken("T"))), g.hms, g.timeSuffix);
		 },
		 dateFormats: function () {
			// pre-loaded rules for different date part order preferences
			var _setfn = function () {
				return  _.set(arguments, g.datePartDelimiter);
			};
			var i,
			RTokenKeys = [
				"d",
				"dd",
				"M",
				"MM",
				"y",
				"yy",
				"yyy",
				"yyyy"
			],
			RToken = [
				/^([0-2]\d|3[0-1]|\d)/,
				/^([0-2]\d|3[0-1])/,
				/^(1[0-2]|0\d|\d)/,
				/^(1[0-2]|0\d)/,
				/^(\d+)/,
				/^(\d\d)/,
				/^(\d\d?\d?\d?)/,
				/^(\d\d\d\d)/
			],
			tokens = [
				t.day,
				t.day,
				t.month,
				t.month,
				t.year,
				t.year,
				t.year,
				t.year
			],
			eachToken = [
				"ordinalSuffix",
				"ordinalSuffix"
			];
			for (i=0; i < RTokenKeys.length; i++) {
				cacheProcessRtoken(RTokenKeys[i], RToken[i], tokens[i], eachToken[i]);
			}

			g.MMM = g.MMMM = _.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"), t.month));
			g.ddd = g.dddd = _.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),
				function (s) {
					return function () {
						this.weekday = s;
					};
				}
			));

			g.day = _fn(g.d, g.dd);
			g.month = _fn(g.M, g.MMM);
			g.year = _fn(g.yyyy, g.yy);

			g.mdy = _setfn(g.ddd, g.month, g.day, g.year);
			g.ymd = _setfn(g.ddd, g.year, g.month, g.day);
			g.dmy = _setfn(g.ddd, g.day, g.month, g.year);
						
			g.date = function (s) {
				return ((g[Date.CultureInfo.dateElementOrder] || g.mdy).call(this, s));
			};
		 },
		 relative: function () {
			// relative date / time expressions
			g.orientation = _.process(g.ctoken("past future"),
				function (s) {
					return function () {
						this.orient = s;
					};
				}
			);

			g.operator = _.process(g.ctoken("add subtract"),
				function (s) {
					return function () {
						this.operator = s;
					};
				}
			);
			g.rday = _.process(g.ctoken("yesterday tomorrow today now"), t.rday);
			g.unit = _.process(g.ctoken("second minute hour day week month year"),
				function (s) {
					return function () {
						this.unit = s;
					};
				}
			);
		 }
	};

	g.buildGrammarFormats = function () {
		// these need to be rebuilt every time the language changes.
		_C = {};

		grammarFormats.timeFormats();
		grammarFormats.dateFormats();
		grammarFormats.relative();

		
		g.value = _.process(_.rtoken(/^([-+]?\d+)?(st|nd|rd|th)?/),
			function (s) {
				return function () {
					this.value = s.replace(/\D/g, "");
				};
			}
		);
		g.expression = _.set([g.rday, g.operator, g.value, g.unit, g.orientation, g.ddd, g.MMM ]);

		g.format = _.process(_.many(
			_.any(
				// translate format specifiers into grammar rules
				_.process(
					_.rtoken(/^(dd?d?d?(?!e)|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),
						function (fmt) {
							if (g[fmt]) {
								return g[fmt];
							} else {
								throw $D.Parsing.Exception(fmt);
							}
						}
					),
					// translate separator tokens into token rules
					_.process(_.rtoken(/^[^dMyhHmstz]+/), // all legal separators 
						function (s) {
							return _.ignore(_.stoken(s));
						}
					)
				)
			),
			// construct the parser ...
			function (rules) {
				return _.process(_.each.apply(null, rules), t.finishExact);
			}
		);

		// starting rule for general purpose grammar
		g._start = _.process(_.set([ g.date, g.time, g.expression ],
		g.generalDelimiter, g.whiteSpace), t.finish);
	};

	g.buildGrammarFormats();
	// parsing date format specifiers - ex: "h:m:s tt" 
	// this little guy will generate a custom parser based
	// on the format string, ex: g.format("h:m:s tt")
	// check for these formats first
	g._formats = g.formats([
		"\"yyyy-MM-ddTHH:mm:ssZ\"",
		"yyyy-MM-ddTHH:mm:ss.sz",
		"yyyy-MM-ddTHH:mm:ssZ",
		"yyyy-MM-ddTHH:mm:ssz",
		"yyyy-MM-ddTHH:mm:ss",
		"yyyy-MM-ddTHH:mmZ",
		"yyyy-MM-ddTHH:mmz",
		"yyyy-MM-ddTHH:mm",
		"ddd, MMM dd, yyyy H:mm:ss tt",
		"ddd MMM d yyyy HH:mm:ss zzz",
		"MMddyyyy",
		"ddMMyyyy",
		"Mddyyyy",
		"ddMyyyy",
		"Mdyyyy",
		"dMyyyy",
		"yyyy",
		"Mdyy",
		"dMyy",
		"d"
	]);
	
	// real starting rule: tries selected formats first, 
	// then general purpose rule
	g.start = function (s) {
		try {
			var r = g._formats.call({}, s);
			if (r[1].length === 0) {
				return r;
			}
		} catch (e) {}
		return g._start.call({}, s);
	};
}());
