#ifndef _PASSENGER_TEMPLATE_H_
#define _PASSENGER_TEMPLATE_H_

#include <string>
#include <cstring>
#include <cstdio>
#include <StaticString.h>
#include <Utils/StrIntUtils.h>
#include <Utils/StringMap.h>

namespace Passenger {

using namespace std;


/**
 * Implements a simple HTML templating language.
 */
class Template {
private:
	typedef string::size_type size_type;

	struct State {
		string result;
		const StringMap<StaticString> &substitutions;

		State(const StaticString &content, const StringMap<StaticString> &_substitutions)
			: result(content.data(), content.size()),
			  substitutions(_substitutions)
			{ }
	};

	struct Options {
		bool raw;
		string defaultValue;

		Options() {
			raw = false;
		}
	};

	StaticString content;

	static bool isNameCharacter(char ch) {
		return (ch >= 'a' && ch <= 'z')
			|| (ch >= 'A' && ch <= 'Z')
			|| (ch >= '0' && ch <= '9')
			|| ch == '_';
	}

	static StaticString readOptionName(const char **current) {
		while (**current != '\0' && (**current == ',' || **current == ' ')) {
			(*current)++;
		}
		const char *begin = *current;
		while (**current != '\0' && isNameCharacter(**current)) {
			(*current)++;
		}
		const char *end = *current;
		return StaticString(begin, end - begin);
	}

	static StaticString readOptionValue(const char **current) {
		while (**current != '\0' && **current == ' ') {
			(*current)++;
		}
		if (**current == '=') {
			(*current)++;
			const char *begin = *current;
			while (**current != '\0' && **current != ',') {
				(*current)++;
			}
			const char *end = *current;
			return StaticString(begin, end - begin);
		} else {
			return StaticString();
		}
	}

	static Options parseOptions(const string &optionsString) {
		Options options;

		const char *current = optionsString.data();
		while (*current != '\0') {
			StaticString name = readOptionName(&current);
			StaticString value = readOptionValue(&current);
			if (name == "raw") {
				options.raw = true;
			} else if (name == "default") {
				options.defaultValue = value;
			} else {
				fprintf(stderr, "Unknown template option '%s'\n", name.c_str());
			}
		}
		return options;
	}

	static string &makeBreakable(string &html) {
		string::size_type i = 0;
		while (i < html.size()) {
			char ch = html[i];
			if (ch == '=' || ch == ',' || ch == ';' || ch == ':') {
				html.insert(i + 1, "<wbr>");
				i += sizeof("<wbr>");
			} else if (ch == '&') {
				// HTML escape character; skip to end.
				do {
					i++;
				} while (i < html.size() && html[i] != ';');
				if (i < html.size()) {
					i++;
				}
			} else {
				i++;
			}
		}
		return html;
	}

	size_type processIf(State &state, size_type pos, size_type conditionEndPos, const string &name) {
		const size_t prefixSize = sizeof("if ") - 1;
		const size_t endIfSize  = sizeof("{{/if}}") - 1;
		const string condition = name.substr(prefixSize, name.size() - prefixSize);
		const string evalResult = state.substitutions.get(condition);

		conditionEndPos += sizeof("}}") - 1;
		size_type endIfPos = state.result.find("{{/if}}", conditionEndPos);
		if (endIfPos == string::npos) {
			return state.result.size();
		}

		if (!evalResult.empty() && evalResult != "false") {
			const string subContent = state.result.substr(conditionEndPos, endIfPos - conditionEndPos);
			State subState(subContent, state.substitutions);
			apply(subState);
			state.result.replace(pos, endIfPos + endIfSize - pos, subState.result);
			return pos + subState.result.size();
		} else {
			state.result.erase(pos, endIfPos - pos + sizeof("{{/if}}") - 1);
			return pos;
		}
	}

	size_type processSubsitution(State &state, size_type pos, size_type endPos, string name) {
		Options options;
		size_type sep = name.find('|');
		if (sep != string::npos) {
			const string optionsString = name.substr(sep + 1);
			name = name.substr(0, sep);
			options = parseOptions(optionsString);
		}

		string value = state.substitutions.get(name);
		if (value.empty()) {
			value = options.defaultValue;
		}
		if (!options.raw) {
			value = escapeHTML(value);
			makeBreakable(value);
		}

		state.result.replace(pos, endPos - pos + (sizeof("}}") - 1), value);
		return pos + value.size();
	}

	size_type processCommand(State &state, size_type pos) {
		size_type endPos = state.result.find("}}", pos);
		if (endPos == string::npos) {
			return state.result.size();
		}

		string name = state.result.substr(pos + 2, endPos - pos - 2);
		if (startsWith(name, "if ")) {
			return processIf(state, pos, endPos, name);
		} else {
			return processSubsitution(state, pos, endPos, name);
		}
	}

	void apply(State &state) {
		size_type searchStart = 0;

		while (searchStart < state.result.size()) {
			size_type pos = state.result.find("{{", searchStart);
			if (pos == string::npos) {
				searchStart = state.result.size();
			} else {
				searchStart = processCommand(state, pos);
			}
		}
	}

public:
	Template(const StaticString &_content)
		: content(_content)
		{ }

	string apply(const StringMap<StaticString> &substitutions) {
		State state(content, substitutions);
		apply(state);
		return state.result;
	}

	static string apply(const StaticString &content, const StringMap<StaticString> &substitutions) {
		return Template(content).apply(substitutions);
	}
};


} // namespace Passenger

#endif /* _PASSENGER_TEMPLATE_H_ */
