//Dokumentation unter ] <nowiki>
/*global mediaWiki*/
(function ($, mw, libs) {
"use strict";

var lastError = '';
function setLastError (error) {
	lastError = error;
}
function getLastError () {
	return lastError;
}

function getInnerRE (template, capturing) {
	var ns, allns = mw.config.get('wgNamespaceIds'),
		templateREs = ,
		templateNameRE = template.replace(/^(.)(.*)$/, function (all, first, rest) {
			var firstLower = first.toLowerCase(), firstUpper = first.toUpperCase();
			if (firstLower !== firstUpper) {
				first = '';
			}
			return first + rest.replace(/+/g, '+');
		}),
		inner = (capturing ? '(' : '(?:') +
			'\\s*(?:<!--(?:+|-|--)*-->\\s*)*\\|(?:+|\\{|\\}|\\{\\{*\\}\\})*)';
	function makeCaseInvariant (c) {
		var upper = c.toUpperCase();
		if (c === '_') {
			return '+';
		} else if (c === upper) {
			return c;
		} else {
			return '';
		}
	}
	for (ns in allns) {
		if (allns === 10) {
			templateREs.push(ns.replace(/./g, makeCaseInvariant));
		}
	}
	return '\\{\\{\\s*(?:(?:' + templateREs.join('|') + ')\\s*:\\s*)?' + templateNameRE + inner + '\\}\\}';
}
function getRE (template, count) {
	var pre = '';
	if (count > 0) {
		pre = '(?:' + getInnerRE(template, false) + '*?){' + count + '}';
	}
	return new RegExp('^(*?' + pre + ')' + getInnerRE(template, true) + '(*)$');
}

function stringX (char, count) {
	var ret = '', i;
	for (i = 0; i < count; i++) {
		ret += char;
	}
	return ret;
}
function mostFrequent (array) {
	var occurences = {}, i, item, maxcount = 0, maxitem = 0;
	for (i = 0; i < array.length; i++) {
		item = array;
		if (occurences === undefined) {
			occurences = 1;
		} else {
			occurences++;
		}
	}
	for (i = 0; i < array.length; i++) {
		item = array;
		if (occurences > maxcount) {
			maxcount = occurences;
			maxitem = item;
		}
	}
	return maxitem;
}

function Template (template, text, count, ignoreDuplicate, allowUnnamed) {
	this.template = template;
	if (this.parse(text, count, ignoreDuplicate, allowUnnamed)) {
		setLastError('');
	}
}

Template.prototype = {
	parse: function (text, count, ignoreDuplicate, allowUnnamed) {
		var re = getRE(this.template, count),
			result, params,
			comments = 0, //Zähler für Kommentare
			unnamed = 1, //Zähler für unbenannte Parameter
			i, index, lastNL, afterNL, pipe, name, equal, val;
		result = re.exec(text.replace(/<!--.*?-->|<nowiki>.*?<\/nowiki>/g, function (all) {
			return all.replace(/\{\{/g, '~~~~open').replace(/\}\}/g, '~~~~close');
		}));
		if (!result) {
			setLastError('template not found');
			return false;
		}
		this.pre = result.replace(/~~~~open/g, '{{').replace(/~~~~close/g, '}}');
		this.post = result.replace(/~~~~open/g, '{{').replace(/~~~~close/g, '}}');

		params = result.replace(/~~~~open/g, '{{').replace(/~~~~close/g, '}}')
			.replace(/<!--\s*(?:\|+=\s*)*-->\s*/g, '')
			.replace(/\n(\s*<!--*?-->)/g, function (all, $1) {
				return '\n|~~~~comment' + String(comments++) + '=' + $1;
			})
			.replace(/<!--.*?-->|<nowiki>.*?<\/nowiki>|\\]|\{\{+\}\}/g, function (c) {
				return c.replace(/\|/g, '~~~~pipe');
			})
			.split('|');
		params.push('');
		for (i = 0; i < params.length; i++) {
			params = params.replace(/~~~~pipe/g, '|');
			if (i === 0) {
				continue;
			}
			if (i !== params.length - 1) {
				params = '|' + params;
			}
			lastNL = params.lastIndexOf('\n');
			if (lastNL === -1) {
				lastNL = params.search(/\s+$/);
			}
			if (lastNL > -1) {
				afterNL = params.slice(lastNL);
				if (/^\s*(?:<!--*-->\s*)*$/.test(afterNL)) {
					params = afterNL + params;
					params = params.slice(0, lastNL);
				}
			}
		}

		this.opening = params;
		this.closing = params.pop();

		this.params = ;
		this.paramVals = {};
		for (i = 1; i < params.length; i++) {
			result = /^(\s*\|\s*)(*)(\s*=\s*)(*)$/.exec(params);
			if (result) {
				pipe = result;
				name = result;
				equal = result;
				val = result;
			} else if (allowUnnamed) {
				result = /^(\s*\|\s*)(*)$/.exec(params);
				pipe = result;
				name = String(unnamed++);
				equal = '=';
				val = result;
			} else {
				setLastError('unnamed parameter');
				return false;
			}
			index = this.params.indexOf(name);
			if (index > -1) {
				if (ignoreDuplicate) {
					this.params.splice(index, 1);
				} else {
					setLastError('duplicate parameter "' + name + '"');
					return false;
				}
			}
			this.params.push(name);
			this.paramVals = {
				pipe: pipe,
				equal: equal,
				val: val
			};
		}
		this.guessIndention();
		return true;
	},
	toString: function () {
		var unnamed = 1;
		return this.pre + '{{' + this.template + this.opening +
			this.params.map(function (name) {
				var paramVal = this.paramVals;
				if (name.indexOf('~~~~comment') === 0) {
					return '\n' + paramVal.val;
				}
				if (name.search(/\D/) === -1 && Number(name) === unnamed && paramVal.val.indexOf('=') === -1) {
					unnamed++;
					return paramVal.pipe + paramVal.val;
				}
				return paramVal.pipe + name + paramVal.equal + paramVal.val;
			}, this).join('') + this.closing + '}}' + this.post;
	},

	guessIndention: function () {
		var nl = , beforePipe = , afterPipe = , beforeEqual = , afterEqual = , trailing = ,
			i, name, paramVal, hasNL, pipePos, pipeLength, equalPos, afterEqualCount;
		for (i = 0; i < this.params.length; i++) {
			name = this.params;
			if (name.indexOf('~~~~comment') === 0) {
				continue;
			}
			paramVal = this.paramVals;
			hasNL = paramVal.pipe.charAt(0) === '\n' ? 1 : 0;
			pipePos = paramVal.pipe.indexOf('|');
			pipeLength = paramVal.pipe.length;
			nl.push(hasNL);
			beforePipe.push(pipePos - hasNL);
			afterPipe.push(pipeLength - pipePos - 1);
			equalPos = paramVal.equal.indexOf('=');
			beforeEqual.push(equalPos);
			beforeEqual.push(-(pipeLength + name.length + equalPos));
			afterEqualCount = paramVal.equal.length - equalPos - 1;
			if (this.getVal(name) === '') {
				if (afterEqualCount === 0) {
					trailing.push(0);
				} else {
					afterEqual.push(afterEqualCount);
					trailing.push(1);
				}
			} else {
				afterEqual.push(afterEqualCount);
			}
		}
		this.indention = [
			mostFrequent(nl),
			mostFrequent(beforePipe),
			mostFrequent(afterPipe),
			mostFrequent(beforeEqual),
			mostFrequent(afterEqual),
			mostFrequent(trailing)
		];
	},
	getPipe: function () {
		return (this.indention === 1 ? '\n' : '') + stringX(' ', this.indention) +
			'|' + stringX(' ', this.indention);
	},
	getEqual: function (p, val) {
		var count1, count2;
		if (this.indention >= 0) {
			count1 = this.indention;
		} else {
			count1 = -this.indention - p.length;
		}
		if (count1 < 0) {
			count1 = 0;
		}
		count2 = this.indention;
		if (val === '' && this.indention === 0) {
			count2 = 0;
		}
		return stringX(' ', count1) + '=' + stringX(' ', count2);
	},

	insert: function (name, val, after, before) {
		if (this.params.indexOf(name) > -1) {
			setLastError('unexpected parameter "' + name + '"');
			return false;
		}
		var index = after ? this.params.indexOf(after) : -1, pipe, equal;
		if (index === -1) {
			index = before ? 0 : this.params.length;
		} else {
			if (!before) {
				index++;
			}
		}
		this.params.splice(index, 0, name);
		pipe = this.getPipe();
		equal = this.getEqual(pipe + name, val);
		this.paramVals = {
			pipe: pipe,
			equal: equal,
			val: val
		};
		return true;
	},
	change: function (name, val) {
		if (this.params.indexOf(name) === -1) {
			setLastError('missing parameter "' + name + '"');
			return false;
		}
		var oldVal = this.paramVals.val;
		this.paramVals.val = val;
		//2 Mal \= um JSHint glücklich zu machen
		if (oldVal === '' && val !== '' && (/\=$/).test(this.paramVals.equal)) {
			this.paramVals.equal += stringX(' ', this.indention);
		} else if (oldVal !== '' && val === '' && this.indention === 0) {
			this.paramVals.equal = this.paramVals.equal.replace(/\=\s+$/, '=');
		}
		return true;
	},
	rename: function (from, to) {
		var index = this.params.indexOf(from), paramVal;
		if (index === -1) {
			setLastError('missing parameter "' + from + '"');
			return false;
		}
		if (this.params.indexOf(to) > -1) {
			setLastError('unexpected parameter "' + to + '"');
			return false;
		}
		this.params = to;
		paramVal = this.paramVals;
		delete this.paramVals;
		paramVal.equal = this.getEqual(paramVal.pipe + to, paramVal.val);
		this.paramVals = paramVal;
		return true;
	},
	remove: function (name) {
		var index = this.params.indexOf(name);
		if (index === -1) {
			setLastError('missing parameter "' + name + '"');
			return false;
		}
		delete this.paramVals;
		this.params.splice(index, 1);
		return true;
	},
	move: function (name, after) {
		var index = this.params.indexOf(name);
		if (index === -1) {
			setLastError('missing parameter "' + name + '"');
			return false;
		}
		this.params.splice(index, 1);
		index = this.params.indexOf(after);
		if (index === -1) {
			index = this.params.length;
		} else {
			index++;
		}
		this.params.splice(index, 0, name);
		return true;
	},

	getIndention: function () {
		return this.indention;
	},
	setIndention: function (a, b, c, d, e, f) {
		if (a !== undefined) {
			this.indention = a;
		}
		if (b !== undefined) {
			this.indention = b;
		}
		if (c !== undefined) {
			this.indention = c;
		}
		if (d !== undefined) {
			this.indention = d;
		}
		if (e !== undefined) {
			this.indention = e;
		}
		if (f !== undefined) {
			this.indention = f;
		}
	},
	normalize: function () {
		var i, name, pipe, equal;
		for (i = 0; i < this.params.length; i++) {
			name = this.params;
			pipe = this.getPipe();
			equal = this.getEqual(pipe + name, this.getVal(name));
			this.paramVals.pipe = pipe;
			this.paramVals.equal = equal;
		}
		this.closing = (this.indention === 1) ? '\n' : '';
	},
	trim: function () {
		var i, name;
		function trimEnd (s) {
			return s.replace(/ +(\n|$)/g, '$1');
		}
		this.opening = trimEnd(this.opening);
		for (i = 0; i < this.params.length; i++) {
			name = this.params;
			this.paramVals.val = trimEnd(this.paramVals.val);
		}
	},

	sort: function (array, acceptUnknown) {
		var newArray = , sortArray = .slice.call(array), i, nextIndex, name;

		for (i = 0; i < this.params.length; i++) {
			if (this.params.indexOf('~~~~comment') === 0) {
				nextIndex = sortArray.indexOf(this.params);
				if (nextIndex > -1) {
					sortArray.splice(nextIndex, 0, this.params);
				}
			}
		}

		for (i = 0; i < sortArray.length; i++) {
			if (this.params.indexOf(sortArray) > -1) {
				newArray.push(sortArray);
			}
		}

		for (i = 0; i < this.params.length; i++) {
			name = this.params;
			if (newArray.indexOf(name) === -1) {
				if (acceptUnknown || name.indexOf('~~~~comment') === 0) {
					newArray.push(name);
				} else {
					setLastError('unknown parameter "' + name + '"');
					return false;
				}
			}
		}
		this.params = newArray;
		return true;
	},
/*
re: regulärer Ausdruck für Wert
optional: true für optionale Werte
allGroup: falls ein Wert dieser Gruppe, so sind alle verpflichtend
oneGroup: genau ein (bzw. höchstens einer bei optional) Wert aus dieser Gruppe
*/
	validate: function (map) {
		var allGroups = {}, oneGroups = {}, test, group, i, name, param, optional;
		for (i = 0; i < this.params.length; i++) {
			name = this.params;
			if (name.indexOf('~~~~comment') === 0) {
				continue;
			}
			test = map;
			if (test === undefined) {
				setLastError('unknown parameter "' + name + '"');
				return false;
			}
			group = test.allGroup;
			if (group) {
				allGroups = true;
			}
			group = test.oneGroup;
			if (group) {
				if (oneGroups) {
					setLastError('duplicate parameters in group "' + group + '"');
					return false;
				}
				oneGroups = true;
			}
			if (test.re) {
				if (!test.re.test(this.paramVals.val)) {
					setLastError('illegal value "' + this.paramVals.val + '" for parameter "' + name + '"');
					return false;
				}
			}
		}
		for (param in map) {
			if (map.hasOwnProperty(param)) {
				test = map;
				optional = test.optional;
				group = test.oneGroup;
				if (!optional && group) {
					if (oneGroups) {
						continue;
					} else {
						setLastError('missing parameter from group "' + group + '"');
						return false;
					}
				}
				group = test.allGroup;
				if (group && allGroups) {
					optional = false;
				}
				if (!optional && this.params.indexOf(param) === -1) {
					setLastError('missing parameter "' + param + '"');
					return false;
				}
			}
		}
		return true;
	},

	getVal: function (name) {
		var paramVal = this.paramVals;
		return paramVal ? paramVal.val : undefined;
	}
};

function TemplateWrapper (template, text, count, ignoreDuplicate, allowUnnamed) {
	var t = new Template(template, text, count, ignoreDuplicate, allowUnnamed);
	if (getLastError() === '') {
		return t;
	}
	if (this instanceof TemplateWrapper) {
		throw new Error(getLastError());
	} else {
		return null;
	}
}
libs.Template = TemplateWrapper;
libs.templateGetLastError = getLastError;

})(jQuery, mediaWiki, mediaWiki.libs);
//</nowiki>