define([
	"dojo/_base/lang",
	"../_base",
	"dojo/_base/array",
	"dojo/_base/connect"
], function(lang,dd,array,connect){

	var ddtl = lang.getObject("tag.loader", true, dd);
	/*=====
	 ddtl = {
	 	// TODO: summary
	 };
	 =====*/

	ddtl.BlockNode = lang.extend(function(name, nodelist){
		this.name = name;
		this.nodelist = nodelist; // Can be overridden
	},
	{
		"super": function(){
			if(this.parent){
				var html = this.parent.nodelist.dummyRender(this.context, null, true);
				if(typeof html == "string"){
					html = new String(html);
				}
				html.safe = true;
				return html;
			}
			return '';
		},
		render: function(context, buffer){
			var name = this.name;
			var nodelist = this.nodelist;
			var parent;
			if(buffer.blocks){
				var block = buffer.blocks[name];
				if(block){
					parent = block.parent;
					nodelist = block.nodelist;
					block.used = true;
				}
			}

			this.rendered = nodelist;

			context = context.push();
			this.context = context;
			this.parent = null;
			if(nodelist != this.nodelist){
				this.parent = this;
			}
			context.block = this;

			if(buffer.getParent){
				var bufferParent = buffer.getParent();
				var setParent = connect.connect(buffer, "onSetParent", function(node, up, root){
					if(up && root){
						buffer.setParent(bufferParent);
					}
				});
			}
			buffer = nodelist.render(context, buffer, this);
			setParent && connect.disconnect(setParent);
			context = context.pop();
			return buffer;
		},
		unrender: function(context, buffer){
			return this.rendered.unrender(context, buffer);
		},
		clone: function(buffer){
			return new this.constructor(this.name, this.nodelist.clone(buffer));
		},
		toString: function(){ return "dojox.dtl.tag.loader.BlockNode"; }
	});

	ddtl.ExtendsNode = lang.extend(function(getTemplate, nodelist, shared, parent, key){
		this.getTemplate = getTemplate;
		this.nodelist = nodelist;
		this.shared = shared;
		this.parent = parent;
		this.key = key;
	},
	{
		parents: {},
		getParent: function(context){
			var parent = this.parent;
			if(!parent){
				var string;
				parent = this.parent = context.get(this.key, false);
				if(!parent){
					throw new Error("extends tag used a variable that did not resolve");
				}
				if(typeof parent == "object"){
					var url = parent.url || parent.templatePath;
					if(parent.shared){
						this.shared = true;
					}
					if(url){
						parent = this.parent = url.toString();
					}else if(parent.templateString){
						// Allow the builder's string interning to work
						string = parent.templateString;
						parent = this.parent = " ";
					}else{
						parent = this.parent = this.parent.toString();
					}
				}
				if(parent && parent.indexOf("shared:") === 0){
					this.shared = true;
					parent = this.parent = parent.substring(7, parent.length);
				}
			}
			if(!parent){
				throw new Error("Invalid template name in 'extends' tag.");
			}
			if(parent.render){
				return parent;
			}
			if(this.parents[parent]){
				return this.parents[parent];
			}
			this.parent = this.getTemplate(string || dojox.dtl.text.getTemplateString(parent));
			if(this.shared){
				this.parents[parent] = this.parent;
			}
			return this.parent;
		},
		render: function(context, buffer){
			var parent = this.getParent(context);

			parent.blocks = parent.blocks || {};
			buffer.blocks = buffer.blocks || {};

			for(var i = 0, node; node = this.nodelist.contents[i]; i++){
				if(node instanceof dojox.dtl.tag.loader.BlockNode){
					var old = parent.blocks[node.name];
					if(old && old.nodelist != node.nodelist){
						// In a shared template, the individual blocks might change
						buffer = old.nodelist.unrender(context, buffer);
					}
					parent.blocks[node.name] = buffer.blocks[node.name] = {
						shared: this.shared,
						nodelist: node.nodelist,
						used: false
					}
				}
			}

			this.rendered = parent;
			return parent.nodelist.render(context, buffer, this);
		},
		unrender: function(context, buffer){
			return this.rendered.unrender(context, buffer, this);
		},
		toString: function(){ return "dojox.dtl.block.ExtendsNode"; }
	});

	ddtl.IncludeNode = lang.extend(function(path, constant, getTemplate, text, parsed){
		this._path = path;
		this.constant = constant;
		this.path = (constant) ? path : new dd._Filter(path);
		this.getTemplate = getTemplate;
		this.text = text;
		this.parsed = (arguments.length == 5) ? parsed : true;
	},
	{
		_cache: [{}, {}],
		render: function(context, buffer){
			var location = ((this.constant) ? this.path : this.path.resolve(context)).toString();
			var parsed = Number(this.parsed);
			var dirty = false;
			if(location != this.last){
				dirty = true;
				if(this.last){
					buffer = this.unrender(context, buffer);
				}
				this.last = location;
			}

			var cache = this._cache[parsed];

			if(parsed){
				if(!cache[location]){
					cache[location] = dd.text._resolveTemplateArg(location, true);
				}
				if(dirty){
					var template = this.getTemplate(cache[location]);
					this.rendered = template.nodelist;
				}
				return this.rendered.render(context, buffer, this);
			}else{
				if(this.text instanceof dd._TextNode){
					if(dirty){
						this.rendered = this.text;
						this.rendered.set(dd.text._resolveTemplateArg(location, true));
					}
					return this.rendered.render(context, buffer);
				}else{
					if(!cache[location]){
						var nodelist = [];
						var div = document.createElement("div");
						div.innerHTML = dd.text._resolveTemplateArg(location, true);
						var children = div.childNodes;
						while(children.length){
							var removed = div.removeChild(children[0]);
							nodelist.push(removed);
						}
						cache[location] = nodelist;
					}
					if(dirty){
						this.nodelist = [];
						var exists = true;
						for(var i = 0, child; child = cache[location][i]; i++){
							this.nodelist.push(child.cloneNode(true));
						}
					}
					for(var i = 0, node; node = this.nodelist[i]; i++){
						buffer = buffer.concat(node);
					}
				}
			}
			return buffer;
		},
		unrender: function(context, buffer){
			if(this.rendered){
				buffer = this.rendered.unrender(context, buffer);
			}
			if(this.nodelist){
				for(var i = 0, node; node = this.nodelist[i]; i++){
					buffer = buffer.remove(node);
				}
			}
			return buffer;
		},
		clone: function(buffer){
			return new this.constructor(this._path, this.constant, this.getTemplate, this.text.clone(buffer), this.parsed);
		}
	});

	lang.mixin(ddtl, {
		block: function(parser, token){
			var parts = token.contents.split();
			var name = parts[1];

			parser._blocks = parser._blocks || {};
			parser._blocks[name] = parser._blocks[name] || [];
			parser._blocks[name].push(name);

			var nodelist = parser.parse(["endblock", "endblock " + name]).rtrim();
			parser.next_token();
			return new dojox.dtl.tag.loader.BlockNode(name, nodelist);
		},
		extends_: function(parser, token){
			var parts = token.contents.split();
			var shared = false;
			var parent = null;
			var key = null;
			if(parts[1].charAt(0) == '"' || parts[1].charAt(0) == "'"){
				parent = parts[1].substring(1, parts[1].length - 1);
			}else{
				key = parts[1];
			}
			if(parent && parent.indexOf("shared:") == 0){
				shared = true;
				parent = parent.substring(7, parent.length);
			}
			var nodelist = parser.parse();
			return new dojox.dtl.tag.loader.ExtendsNode(parser.getTemplate, nodelist, shared, parent, key);
		},
		include: function(parser, token){
			var parts = token.contents.split();
			if(parts.length != 2){
				throw new Error(parts[0] + " tag takes one argument: the name of the template to be included");
			}
			var path = parts[1];
			var constant = false;
			if((path.charAt(0) == '"' || path.slice(-1) == "'") && path.charAt(0) == path.slice(-1)){
				path = path.slice(1, -1);
				constant = true;
			}
			return new ddtl.IncludeNode(path, constant, parser.getTemplate, parser.create_text_node());
		},
		ssi: function(parser, token){
			// We're going to treat things a little differently here.
			// First of all, this tag is *not* portable, so I'm not
			// concerned about it being a "drop in" replacement.

			// Instead, we'll just replicate the include tag, but with that
			// optional "parsed" parameter.
			var parts = token.contents.split();
			var parsed = false;
			if(parts.length == 3){
				parsed = (parts.pop() == "parsed");
				if(!parsed){
					throw new Error("Second (optional) argument to ssi tag must be 'parsed'");
				}
			}
			var node = ddtl.include(parser, new dd.Token(token.token_type, parts.join(" ")));
			node.parsed = parsed;
			return node;
		}
	});

	return ddtl;
});