Prism.languages['markup-templating'] = {};

Object.defineProperties(Prism.languages['markup-templating'], {
	buildPlaceholders: {
		// Tokenize all inline templating expressions matching placeholderPattern
		// If the replaceFilter function is provided, it will be called with every match.
		// If it returns false, the match will not be replaced.
		value: function (env, language, placeholderPattern, replaceFilter) {
			if (env.language !== language) {
				return;
			}

			env.tokenStack = [];

			env.code = env.code.replace(placeholderPattern, function(match) {
				if (typeof replaceFilter === 'function' && !replaceFilter(match)) {
					return match;
				}
				var i = env.tokenStack.length;
				// Check for existing strings
				while (env.code.indexOf('___' + language.toUpperCase() + i + '___') !== -1)
					++i;

				// Create a sparse array
				env.tokenStack[i] = match;

				return '___' + language.toUpperCase() + i + '___';
			});

			// Switch the grammar to markup
			env.grammar = Prism.languages.markup;
		}
	},
	tokenizePlaceholders: {
		// Replace placeholders with proper tokens after tokenizing
		value: function (env, language) {
			if (env.language !== language || !env.tokenStack) {
				return;
			}

			// Switch the grammar back
			env.grammar = Prism.languages[language];

			var j = 0;
			var keys = Object.keys(env.tokenStack);
			var walkTokens = function (tokens) {
				if (j >= keys.length) {
					return;
				}
				for (var i = 0; i < tokens.length; i++) {
					var token = tokens[i];
					if (typeof token === 'string' || (token.content && typeof token.content === 'string')) {
						var k = keys[j];
						var t = env.tokenStack[k];
						var s = typeof token === 'string' ? token : token.content;

						var index = s.indexOf('___' + language.toUpperCase() + k + '___');
						if (index > -1) {
							++j;
							var before = s.substring(0, index);
							var middle = new Prism.Token(language, Prism.tokenize(t, env.grammar, language), 'language-' + language, t);
							var after = s.substring(index + ('___' + language.toUpperCase() + k + '___').length);
							var replacement;
							if (before || after) {
								replacement = [before, middle, after].filter(function (v) { return !!v; });
								walkTokens(replacement);
							} else {
								replacement = middle;
							}
							if (typeof token === 'string') {
								Array.prototype.splice.apply(tokens, [i, 1].concat(replacement));
							} else {
								token.content = replacement;
							}

							if (j >= keys.length) {
								break;
							}
						}
					} else if (token.content && typeof token.content !== 'string') {
						walkTokens(token.content);
					}
				}
			};

			walkTokens(env.tokens);
		}
	}
});