/*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
/*
 * Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup.
 *
 * Copyright 2011, Boris Moore
 * Released under the MIT License.
 */
window.JsViews || window.jQuery && jQuery.views || (function( window, undefined ) {

var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend,
	FALSE = false, TRUE = true,
	jQuery = window.jQuery, document = window.document,
	htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
	rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g,
	rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g,
	rNewLine = /\r?\n/g,
	rUnescapeQuotes = /\\(['"])/g,
	rEscapeQuotes = /\\?(['"])/g,
	rBuildHash = /\x08([^\x08]+)\x08/g,
	autoName = 0,
	escapeMapForHtml = {
		"&": "&amp;",
		"<": "&lt;",
		">": "&gt;"
	},
	htmlSpecialChar = /[\x00"&'<>]/g,
	slice = Array.prototype.slice;

if ( jQuery ) {

	////////////////////////////////////////////////////////////////////////////////////////////////
	// jQuery is loaded, so make $ the jQuery object
	$ = jQuery;

	$.fn.extend({
		// Use first wrapped element as template markup.
		// Return string obtained by rendering the template against data.
		render: function( data, context, parentView, path ) {
			return render( data, this[0], context, parentView, path );
		},

		// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
		template: function( name, context ) {
			return $.template( name, this[0], context );
		}
	});

} else {

	////////////////////////////////////////////////////////////////////////////////////////////////
	// jQuery is not loaded. Make $ the JsViews object

	// Map over the $ in case of overwrite
	_$ = window.$;

	window.JsViews = JsViews = window.$ = $ = {
		extend: function( target, source ) {
			var name;
			for ( name in source ) {
				target[ name ] = source[ name ];
			}
			return target;
		},
		isArray: Array.isArray || function( obj ) {
			return Object.prototype.toString.call( obj ) === "[object Array]";
		},
		noConflict: function() {
			if ( window.$ === JsViews ) {
				window.$ = _$;
			}
			return JsViews;
		}
	};
}

extend = $.extend;

//=================
// View constructor
//=================

function View( context, path, parentView, data, template ) {
	// Returns a view data structure for a new rendered instance of a template.
	// The content field is a hierarchical array of strings and nested views.

	parentView = parentView || { viewsCount:0, ctx: viewsNs.helpers };

	var parentContext = parentView && parentView.ctx;

	return {
		jsViews: "v1.0pre",
		path: path || "",
		// inherit context from parentView, merged with new context.
		itemNumber: ++parentView.viewsCount || 1,
		viewsCount: 0,
		tmpl: template,
		data: data || parentView.data || {},
		// Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views)
		ctx : context && context === parentContext
			? parentContext
			: (parentContext ? extend( extend( {}, parentContext ), context ) : context||{}), 
			// If no jQuery, extend does not support chained copies - so limit to two parameters
		parent: parentView
	};
}
extend( $, {
	views: viewsNs = {
		templates: {},
		tags: {
			"if": function() {
				var ifTag = this,
					view = ifTag._view;
				view.onElse = function( presenter, args ) {
					var i = 0,
						l = args.length;
					while ( l && !args[ i++ ]) {
						// Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
						if ( i === l ) {
							return "";
						}
					}
					view.onElse = undefined; // If condition satisfied, so won't run 'else'.
					return render( view.data, presenter.tmpl, view.ctx, view);
				};
				return view.onElse( this, arguments );
			},
			"else": function() {
				var view = this._view;
				return view.onElse ? view.onElse( this, arguments ) : "";
			},
			each: function() {
				var i, 
					self = this,
					result = "",
					args = arguments,
					l = args.length,
					content = self.tmpl,
					view = self._view;
				for ( i = 0; i < l; i++ ) {
					result += args[ i ] ? render( args[ i ], content, self.ctx || view.ctx, view, self._path, self._ctor ) : "";
				}
				return l ? result 
					// If no data parameter, use the current $data from view, and render once
					:  result + render( view.data, content, view.ctx, view, self._path, self.tag );
			},
			"=": function( value ) {
				return value;
			},
			"*": function( value ) {
				return value;
			}
		},
		helpers: {
			not: function( value ) {
				return !value;
			}
		},
		allowCode: FALSE,
		debugMode: TRUE,
		err: function( e ) {
			return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>"): '""';
		},

//===============
// setDelimiters
//===============

		setDelimiters: function( openTag, closeTag ) {
			// Set or modify the delimiter characters for tags: "{{" and "}}"
			var firstCloseChar = closeTag.charAt( 0 ),
				secondCloseChar = closeTag.charAt( 1 );
			openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 );
			closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;

			// Build regex with new delimiters
			//           {{
			rTag = openTag
				//       #      tag    (followed by space,! or })             or equals or  code
				+ "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))"
				//     params
				+ "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)"
				//   encoding
				+ "(!(\\w*))?"
				//        closeBlock
				+ "|(?:\\/([\\w\\$\\.\\[\\]]+)))"
			//  }}
			+ closeTag;

			// Default rTag:     #    tag              equals code        params         encoding    closeBlock
			//      /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;

			rTag = new RegExp( rTag, "g" );
		},


//===============
// registerTags
//===============

		// Register declarative tag.
		registerTags: registerTags = function( name, tagFn ) {
			var key;
			if ( typeof name === "object" ) {
				for ( key in name ) {
					registerTags( key, name[ key ]);
				}
			} else {
				// Simple single property case.
				viewsNs.tags[ name ] = tagFn;
			}
			return this;
		},

//===============
// registerHelpers
//===============

		// Register helper function for use in markup.
		registerHelpers: registerHelpers = function( name, helper ) {
			if ( typeof name === "object" ) {
				// Object representation where property name is path and property value is value.
				// TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
				var key;
				for ( key in name ) {
					registerHelpers( key, name[ key ]);
				}
			} else {
				// Simple single property case.
				viewsNs.helpers[ name ] = helper;
			}
			return this;
		},

//===============
// tmpl.encode
//===============

		encode: function( encoding, text ) {
			return text
				? ( tmplEncode[ encoding || "html" ] || tmplEncode.html)( text ) // HTML encoding is the default
				: "";
		},

		encoders: tmplEncode = {
			"none": function( text ) {
				return text;
			},
			"html": function( text ) {
				// HTML encoding helper: Replace < > & and ' and " by corresponding entities.
				// Implementation, from Mike Samuel <msamuel@google.com>
				return String( text ).replace( htmlSpecialChar, replacerForHtml );
			}
			//TODO add URL encoding, and perhaps other encoding helpers...
		},

//===============
// renderTag
//===============

		renderTag: function( tag, view, encode, content, tagProperties ) {
			// This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...]
			var ret, ctx, name,
				args = arguments,
				presenters = viewsNs.presenters;
				hash = tagProperties._hash,
				tagFn = viewsNs.tags[ tag ];

			if ( !tagFn ) {
				return "";
			}
			
			content = content && view.tmpl.nested[ content - 1 ];
			tagProperties.tmpl = tagProperties.tmpl || content || undefined;
			// Set the tmpl property to the content of the block tag, unless set as an override property on the tag
		
			if ( presenters && presenters[ tag ]) {
				ctx = extend( extend( {}, tagProperties.ctx ), tagProperties );  
				delete ctx.ctx;  
				delete ctx._path;  
				delete ctx.tmpl;
				tagProperties.ctx = ctx;  
				tagProperties._ctor = tag + (hash ? "=" + hash.slice( 0, -1 ) : "");

				tagProperties = extend( extend( {}, tagFn ), tagProperties );
				tagFn = viewsNs.tags.each; // Use each to render the layout template against the data
			} 

			tagProperties._encode = encode;
			tagProperties._view = view;
			ret = tagFn.apply( tagProperties, args.length > 5 ? slice.call( args, 5 ) : [view.data] );
			return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string) 
		}
	},

//===============
// render
//===============

	render: render = function( data, tmpl, context, parentView, path, tagName ) {
		// Render template against data as a tree of subviews (nested template), or as a string (top-level template).
		// tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
		var i, l, dataItem, arrayView, content, result = "";

		if ( arguments.length === 2 && data.jsViews ) {
			parentView = data;
			context = parentView.ctx;
			data = parentView.data;
		}
		tmpl = $.template( tmpl );
		if ( !tmpl ) {
			return ""; // Could throw...
		}

		if ( $.isArray( data )) {
			// Create a view item for the array, whose child views correspond to each data item.
			arrayView = new View( context, path, parentView, data);
			l = data.length;
			for ( i = 0, l = data.length; i < l; i++ ) {
				dataItem = data[ i ];
				content = dataItem ? tmpl( dataItem, new View( context, path, arrayView, dataItem, tmpl, this )) : "";
				result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content;
			}
		} else {
			result += tmpl( data, new View( context, path, parentView, data, tmpl ));
		}

		return viewsNs.activeViews
			// If in activeView mode, include annotations
			? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
			// else return just the string result
			: result;
	},

//===============
// template
//===============

	template: function( name, tmpl ) {
		// Set:
		// Use $.template( name, tmpl ) to cache a named template,
		// where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
		// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.

		// Get:
		// Use $.template( name ) to access a cached template.
		// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
		// will return the compiled template, without adding a name reference.
		// If templateString is not a selector, $.template( templateString ) is equivalent
		// to $.template( null, templateString ). To ensure a string is treated as a template,
		// include an HTML element, an HTML comment, or a template comment tag.

		if (tmpl) {
			// Compile template and associate with name
			if ( "" + tmpl === tmpl ) { // type string
				// This is an HTML string being passed directly in.
				tmpl = compile( tmpl );
			} else if ( jQuery && tmpl instanceof $ ) {
				tmpl = tmpl[0];
			}
			if ( tmpl ) {
				if ( jQuery && tmpl.nodeType ) {
					// If this is a template block, use cached copy, or generate tmpl function and cache.
					tmpl = $.data( tmpl, "tmpl" ) || $.data( tmpl, "tmpl", compile( tmpl.innerHTML ));
				}
				viewsNs.templates[ tmpl._name = tmpl._name || name || "_" + autoName++ ] = tmpl;
			}
			return tmpl;
		}
		// Return named compiled template
		return name
			? "" + name !== name // not type string
				? (name._name
					? name // already compiled
					: $.template( null, name ))
				: viewsNs.templates[ name ] ||
					// If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
					$.template( null, htmlExpr.test( name ) ? name : try$( name ))
			: null;
	}
});

viewsNs.setDelimiters( "{{", "}}" );

//=================
// compile template
//=================

// Generate a reusable function that will serve to render a template against data
// (Compile AST then build template function)

function parsePath( all, comp, object, viewDataCtx, viewProperty, path, string, quot ) {
	return object
		? ((viewDataCtx
			? viewProperty
				? ("$view." + viewProperty)
				: object
			:("$data." + object)
		)  + ( path || "" ))
		: string || (comp || "");
}

function compile( markup ) {
	var newNode,
		loc = 0,
		stack = [],
		topNode = [],
		content = topNode,
		current = [,,topNode];

	function pushPreceedingContent( shift ) {
		shift -= loc;
		if ( shift ) {
			content.push( markup.substr( loc, shift ).replace( rNewLine,"\\n"));
		}
	}

	function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) {
		// rTag    :    #    tagName          equals code        params         encode      closeBlock
		// /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;

		// Build abstract syntax tree: [ tagName, params, content, encode ]
		var named,
			hash = "",
			parenDepth = 0,
			quoted = FALSE, // boolean for string content in double qoutes
			aposed = FALSE; // or in single qoutes

		function parseParams( all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index ) {
			//      path          paren eq      comma   apos   quot  rtPrn  space
			// /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g

			return aposed
				// within single-quoted string
				? ( aposed = !apos, (aposed ? all : '"'))
				: quoted
					// within double-quoted string
					? ( quoted = !quot, (quoted ? all : '"'))
					: comp
						// comparison
						? ( path.replace( rPath, parsePath ) + comp)
						: eq
							// named param
							? parenDepth ? "" :( named = TRUE, '\b' + path + ':')
							: paren
								// function
								? (parenDepth++, path.replace( rPath, parsePath ) + '(')
								: rightParen
									// function
									? (parenDepth--, ")")
									: path
										// path
										? path.replace( rPath, parsePath )
										: comma
											? ","
											: space
												? (parenDepth
													? ""
													: named
														? ( named = FALSE, "\b")
														: ","
												)
												: (aposed = apos, quoted = quot, '"');
		}

		tagName = tagName || equals;
		pushPreceedingContent( index );
		if ( code ) {
			if ( viewsNs.allowCode ) {
				content.push([ "*", params.replace( rUnescapeQuotes, "$1" )]);
			}
		} else if ( tagName ) {
			if ( tagName === "else" ) {
				current = stack.pop();
				content = current[ 2 ];
				isBlock = TRUE;
			}
			params = (params
				? (params + " ")
					.replace( rParams, parseParams )
					.replace( rBuildHash, function( all, keyValue, index ) {
						hash += keyValue + ",";
						return "";
					})
				: "");
			params = params.slice( 0, -1 );
			newNode = [
				tagName,
				useEncode ? encode || "none" : "",
				isBlock && [],
				"{" + hash + "_hash:'" +  hash + "',_path:'" + params + "'}",
				params
			];

			if ( isBlock ) {
				stack.push( current );
				current = newNode;
			}
			content.push( newNode );
		} else if ( closeBlock ) {
			current = stack.pop();
		}
		loc = index + all.length; // location marker - parsed up to here
		if ( !current ) {
			throw "Expected block tag";
		}
		content = current[ 2 ];
	}
	markup = markup.replace( rEscapeQuotes, "\\$1" );
	markup.replace( rTag, parseTag );
	pushPreceedingContent( markup.length );
	return buildTmplFunction( topNode );
}

// Build javascript compiled template function, from AST
function buildTmplFunction( nodes ) {
	var ret, node, i,
		nested = [],
		l = nodes.length,
		code = "try{var views="
			+ (jQuery ? "jQuery" : "JsViews")
			+ '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n';

	for ( i = 0; i < l; i++ ) {
		node = nodes[ i ];
		if ( node[ 0 ] === "*" ) {
			code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + ( i + 1 < l ? "result+=" : "" );
		} else if ( "" + node === node ) { // type string
			code += '"' + node + '"+';
		} else {
			var tag = node[ 0 ],
				encode = node[ 1 ],
				content = node[ 2 ],
				obj = node[ 3 ],
				params = node[ 4 ],
				paramsOrEmptyString = params + '||"")+';

			if( content ) {
				nested.push( buildTmplFunction( content ));
			}
			code += tag === "="
				? (!encode || encode === "html"
					? "html(" + paramsOrEmptyString
					: encode === "none"
						? ("(" + paramsOrEmptyString)
						: ('enc("' + encode + '",' + paramsOrEmptyString)
				)
				: 'tag("' + tag + '",$view,"' + ( encode || "" ) + '",'
					+ (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
					+ "," + obj + (params ? "," : "") + params + ")+";
		}
	}
	ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" );
	ret.nested = nested;
	return ret;
}

//========================== Private helper functions, used by code above ==========================

function replacerForHtml( ch ) {
	// Original code from Mike Samuel <msamuel@google.com>
	return escapeMapForHtml[ ch ]
		// Intentional assignment that caches the result of encoding ch.
		|| ( escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";" );
}

function try$( selector ) {
	// If selector is valid, return jQuery object, otherwise return (invalid) selector string
	try {
		return $( selector );
	} catch( e) {}
	return selector;
}
})( window );

