/*
  Copyright 2012, Robert Knight

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

    Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
*/

#include "mustache.h"
#include "../charcode.h"
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QStringList>
#include <QtCore/QTextStream>
#include <QDataStream>

using namespace Mustache;

QString Mustache::renderTemplate(const QString& templateString, const QVariantHash& args)
{
	Mustache::QtVariantContext context(args);
	Mustache::Renderer renderer;
	return renderer.render(templateString, &context);
}

QString escapeHtml(const QString& input)
{
	QString escaped(input);
	for (int i=0; i < escaped.count();) {
		const char* replacement = 0;
		ushort ch = escaped.at(i).unicode();
		if (ch == '&') {
			replacement = "&amp;";
		} else if (ch == '<') {
			replacement = "&lt;";
		} else if (ch == '>') {
			replacement = "&gt;";
		} else if (ch == '"') {
			replacement = "&quot;";
		}
		if (replacement) {
			escaped.replace(i, 1, QLatin1String(replacement));
			i += (int)strlen(replacement);
		} else {
			++i;
		}
	}
	return escaped;
}

QString unescapeHtml(const QString& escaped)
{
	QString unescaped(escaped);
	unescaped.replace(QLatin1String("&lt;"), QLatin1String("<"));
	unescaped.replace(QLatin1String("&gt;"), QLatin1String(">"));
	unescaped.replace(QLatin1String("&quot;"), QLatin1String("\""));
	unescaped.replace(QLatin1String("&amp;"), QLatin1String("&"));
	return unescaped;
}

Context::Context(PartialResolver* resolver)
	: m_partialResolver(resolver)
{}

PartialResolver* Context::partialResolver() const
{
	return m_partialResolver;
}

QString Context::partialValue(const QString& key) const
{
	if (!m_partialResolver) {
		return QString();
	}
	return m_partialResolver->getPartial(key);
}

bool Context::canEval(const QString&) const
{
	return false;
}

QString Context::eval(const QString& key, const QString& _template, Renderer* renderer)
{
	Q_UNUSED(key);
	Q_UNUSED(_template);
	Q_UNUSED(renderer);

	return QString();
}

QtVariantContext::QtVariantContext(const QVariant& root, PartialResolver* resolver)
	: Context(resolver)
{
	m_contextStack << root;
}

QVariant variantMapValue(const QVariant& value, const QString& key)
{
	if (value.userType() == QVariant::Map) {
		return value.toMap().value(key);
	} else {
		return value.toHash().value(key);
	}
}

QVariant variantMapValueForKeyPath(const QVariant& value, const QStringList keyPath)
{
	if (keyPath.count() > 1) {
		QVariant firstValue = variantMapValue(value, keyPath.first());
		return firstValue.isNull() ? QVariant() : variantMapValueForKeyPath(firstValue, keyPath.mid(1));
	} else if (!keyPath.isEmpty()) {
		return variantMapValue(value, keyPath.first());
	}
	return QVariant();
}

QVariant QtVariantContext::value(const QString& key) const
{
	if (key == "." && !m_contextStack.isEmpty()) {
		return m_contextStack.last();
	}
	QStringList keyPath = key.split(".");
	for (int i = m_contextStack.count()-1; i >= 0; i--) {
		QVariant value = variantMapValueForKeyPath(m_contextStack.at(i), keyPath);
		if (!value.isNull()) {
			return value;
		}
	}
	return QVariant();
}

bool QtVariantContext::isFalse(const QString& key) const
{
	QVariant value = this->value(key);
	switch (value.userType()) {
	case QMetaType::QChar:
	case QMetaType::Double:
	case QMetaType::Float:
	case QMetaType::Int:
	case QMetaType::UInt:
	case QMetaType::LongLong:
	case QMetaType::ULongLong:
	case QVariant::Bool:
		return !value.toBool();
	case QVariant::List:
	case QVariant::StringList:
		return value.toList().isEmpty();
	case QVariant::Hash:
		return value.toHash().isEmpty();
	case QVariant::Map:
		return value.toMap().isEmpty();
	default:
		return value.toString().isEmpty();
	}
}

QString QtVariantContext::stringValue(const QString& key) const
{
	if (isFalse(key) && value(key).userType() != QVariant::Bool) {
		return QString();
	}
	return value(key).toString();
}

qint8 QtVariantContext::charValue(const QString& key) const
{
	QString val = value(key).toString();
	return val.toInt();
}

QByteArray QtVariantContext::bytesValue(const QString& key) const
{
	return value(key).toByteArray();
}

quint32 QtVariantContext::uintValue(const QString& key) const
{
	return value(key).toUInt();
}

qint32 QtVariantContext::intValue(const QString& key) const
{
	//qDebug() << "[DBG] key:" << key << ", intValue:" << value(key).toInt();
	return value(key).toInt();
}

quint16 QtVariantContext::ushortValue(const QString& key) const
{
	return (quint16)value(key).toUInt();
}

qint16 QtVariantContext::shortValue(const QString& key) const
{
	return (qint16)value(key).toInt();
}

float QtVariantContext::floatValue(const QString& key) const
{
	return value(key).toFloat();
}

double QtVariantContext::doubleValue(const QString& key) const
{
	return value(key).toDouble();
}

void QtVariantContext::push(const QString& key, int index)
{
	QVariant mapItem = value(key);
	if (index == -1) {
		m_contextStack << mapItem;
	} else {
		QVariantList list = mapItem.toList();
		m_contextStack << list.value(index, QVariant());
	}
}

void QtVariantContext::pop()
{
	m_contextStack.pop();
}

int QtVariantContext::listCount(const QString& key) const
{
	const QVariant& item = value(key);
	if (item.canConvert<QVariantList>()) {
		return item.toList().count();
	}
	return 0;
}

bool QtVariantContext::canEval(const QString& key) const
{
	return value(key).canConvert<fn_t>();
}

QString QtVariantContext::eval(const QString& key, const QString& _template, Renderer* renderer)
{
	QVariant fn = value(key);
	if (fn.isNull()) {
		return QString();
	}
	return fn.value<fn_t>()(_template, renderer, this);
}

PartialMap::PartialMap(const QHash<QString, QString>& partials)
	: m_partials(partials)
{}

QString PartialMap::getPartial(const QString& name)
{
	return m_partials.value(name);
}

PartialFileLoader::PartialFileLoader(const QString& basePath)
	: m_basePath(basePath)
{}

QString PartialFileLoader::getPartial(const QString& name)
{
	if (!m_cache.contains(name)) {
		QString path = m_basePath + '/' + name + ".mustache";
		QFile file(path);
		if (file.open(QIODevice::ReadOnly)) {
			QTextStream stream(&file);
			m_cache.insert(name, stream.readAll());
		}
	}
	return m_cache.value(name);
}

Renderer::Renderer()
	: m_errorPos(-1)
	, m_defaultTagStartMarker("{{")
	, m_defaultTagEndMarker("}}")
{
}

QString Renderer::error() const
{
	return m_error;
}

int Renderer::errorPos() const
{
	return m_errorPos;
}

QString Renderer::errorPartial() const
{
	return m_errorPartial;
}

QVector<QPair<QString, Tag>> Renderer::getInputFields(const QString& _template)
{
	QVector<QPair<QString, Tag>> result;

	m_error.clear();
	m_errorPos = -1;
	m_errorPartial.clear();

	m_tagStartMarker = m_defaultTagStartMarker;
	m_tagEndMarker = m_defaultTagEndMarker;

	int lastTagEnd = 0;

	while (m_errorPos == -1) {
		Tag tag = findTag(_template, lastTagEnd, _template.length());
		if (tag.type == Tag::Null) {
			//ds << _template.midRef(lastTagEnd, endPos - lastTagEnd);
			break;
		}

		//qDebug() << "----->" << tag.key;
		if (!tag.label.isEmpty())
		{
			QPair<QString, Tag> field = qMakePair(tag.key, tag);
			result << field;
		}

		lastTagEnd = tag.end;
	}

	return result;
}

QVariantHash Renderer::getFixedValue(const QString& _template)
{
	QVariantHash result;

	m_error.clear();
	m_errorPos = -1;
	m_errorPartial.clear();

	m_tagStartMarker = m_defaultTagStartMarker;
	m_tagEndMarker = m_defaultTagEndMarker;

	int lastTagEnd = 0;

	while (m_errorPos == -1) {
		Tag tag = findTag(_template, lastTagEnd, _template.length());
		if (tag.type == Tag::Null) {
			//ds << _template.midRef(lastTagEnd, endPos - lastTagEnd);
			break;
		}

		//qDebug() << "----->" << tag.key;
		if (!tag.value.isNull())
		{
			result[tag.key] = tag.value;
		}

		lastTagEnd = tag.end;
	}

	return result;
}

QByteArray Renderer::renderBytes(const QString& _template, Context* context)
{
	m_error.clear();
	m_errorPos = -1;
	m_errorPartial.clear();

	m_tagStartMarker = m_defaultTagStartMarker;
	m_tagEndMarker = m_defaultTagEndMarker;

	return renderBytes(_template, 0, _template.length(), context);
}

QByteArray Renderer::renderBytes(const QString& _template, int startPos, int endPos, Context* context)
{
	QByteArray output;
	QDataStream ds(&output, QIODevice::WriteOnly);
	ds.setByteOrder(QDataStream::LittleEndian);
	ds.setFloatingPointPrecision(QDataStream::SinglePrecision);

	int lastTagEnd = startPos;

	while (m_errorPos == -1) {
		Tag tag = findTag(_template, lastTagEnd, endPos);
		if (tag.type == Tag::Null) {
			//ds << _template.midRef(lastTagEnd, endPos - lastTagEnd);
			break;
		}

		switch (tag.type) {
		case Tag::Value:
		case Tag::Input:
		{
			if (tag.valueType == Tag::CHAR)
			{
				if (tag.valueLength == 1)
					ds << context->charValue(tag.key);
				else if (tag.valueLength > 1){
					QString str = context->stringValue(tag.key);//.rightJustified(tag.valueLength, '\0');
					QByteArray ba = str.toLocal8Bit().leftJustified(tag.valueLength, '\0');
					qDebug() << ba;
					ds.writeRawData(ba, tag.valueLength);
				}					
			}
			else if(tag.valueType == Tag::UINT)
			{
				ds << context->uintValue(tag.key);
			}
			else if (tag.valueType == Tag::INT)
			{
				ds << context->intValue(tag.key);
			}
			else if (tag.valueType == Tag::USHORT)
			{
				ds << context->ushortValue(tag.key);
			}
			else if (tag.valueType == Tag::SHORT)
			{
				ds << context->shortValue(tag.key);
			}
			else if (tag.valueType == Tag::FLOAT)
			{
				ds << context->floatValue(tag.key);
			}
			else if (tag.valueType == Tag::DOUBLE)
			{
				ds << context->doubleValue(tag.key);
			}
			else if (tag.valueType == Tag::STR)
			{
				QString str = context->stringValue(tag.key);
				ds << str.length()  << str;
			}
			QString value = context->stringValue(tag.key);
			if (tag.escapeMode == Tag::Escape) {
				value = escapeHtml(value);
			}
			else if (tag.escapeMode == Tag::Unescape) {
				value = unescapeHtml(value);
			}
			output += value;
			lastTagEnd = tag.end;
		}
		break;
		case Tag::SectionStart:
		{
			Tag endTag = findEndTag(_template, tag, endPos);
			if (endTag.type == Tag::Null) {
				if (m_errorPos == -1) {
					setError("No matching end tag found for section", tag.start);
				}
			}
			else {
				int listCount = context->listCount(tag.key);
				if (listCount > 0) {
					for (int i = 0; i < listCount; i++) {
						context->push(tag.key, i);
						QByteArray ba = renderBytes(_template, tag.end, endTag.start, context);
						ds.writeRawData(ba.data(), ba.size());
						context->pop();
					}
				}
				else if (context->canEval(tag.key)) {
					// ds << context->eval(tag.key, _template.mid(tag.end, endTag.start - tag.end), this);
				}
				else if (!context->isFalse(tag.key)) {
					context->push(tag.key);
					QByteArray ba = renderBytes(_template, tag.end, endTag.start, context);
					ds.writeRawData(ba.data(), ba.size());
					context->pop();
				}
				lastTagEnd = endTag.end;
			}
		}
		break;
		case Tag::InvertedSectionStart:
		{
			Tag endTag = findEndTag(_template, tag, endPos);
			if (endTag.type == Tag::Null) {
				if (m_errorPos == -1) {
					setError("No matching end tag found for inverted section", tag.start);
				}
			}
			else {
				if (context->isFalse(tag.key)) {
					QByteArray ba = renderBytes(_template, tag.end, endTag.start, context);
					ds.writeRawData(ba.data(), ba.size());
				}
				lastTagEnd = endTag.end;
			}
		}
		break;
		case Tag::SectionEnd:
			setError("Unexpected end tag", tag.start);
			lastTagEnd = tag.end;
			break;
		case Tag::Partial:
		{
			QString tagStartMarker = m_tagStartMarker;
			QString tagEndMarker = m_tagEndMarker;

			m_tagStartMarker = m_defaultTagStartMarker;
			m_tagEndMarker = m_defaultTagEndMarker;

			m_partialStack.push(tag.key);

			QString partialContent = context->partialValue(tag.key);

			// If there is a need to add a special indentation to the partial
			if (tag.indentation > 0) {
				//output += QString(" ").repeated(tag.indentation);
				// Indenting the output to keep the parent indentation.
				int posOfLF = partialContent.indexOf("\n", 0);
				while (posOfLF > 0 && posOfLF < (partialContent.length() - 1)) { // .length() - 1 because we dont want indentation AFTER the last character if it's a LF
					partialContent = partialContent.insert(posOfLF + 1, QString(" ").repeated(tag.indentation));
					posOfLF = partialContent.indexOf("\n", posOfLF + 1);
				}
			}

			QByteArray partialRendered = renderBytes(partialContent, 0, partialContent.length(), context);
			ds.writeRawData(partialRendered.data(), partialRendered.size());

			lastTagEnd = tag.end;

			m_partialStack.pop();

			m_tagStartMarker = tagStartMarker;
			m_tagEndMarker = tagEndMarker;
		}
		break;
		case Tag::SetDelimiter:
			lastTagEnd = tag.end;
			break;
		case Tag::Comment:
			lastTagEnd = tag.end;
			break;
		case Tag::Null:
			break;
		}
	}

	return output;
}

QVariantHash Renderer::parseBytes(const QString &_template, const QByteArray &array)
{
	m_error.clear();
	m_errorPos = -1;
	m_errorPartial.clear();

	m_tagStartMarker = m_defaultTagStartMarker;
	m_tagEndMarker = m_defaultTagEndMarker;
	return parseBytes(_template, 0, _template.size(), array);
}

QVariantHash Renderer::parseBytes(const QString &_template, int startPos, int endPos, const QByteArray &array)
{
	QVariantHash values;
	QDataStream ds(array);
	ds.setByteOrder(QDataStream::BigEndian);
	ds.setFloatingPointPrecision(QDataStream::SinglePrecision);

	int lastTagEnd = startPos;

	while (lastTagEnd != endPos) {
		if (ds.atEnd())
			break;

		Tag tag = findTag(_template, lastTagEnd, endPos);
		if (tag.type == Tag::Null)
			break;

		if (tag.valueType == Tag::CHAR)
		{
			if (tag.valueLength <= 0)
				continue;
			char *value = new char[tag.valueLength + 1];
			memset(value, 0, tag.valueLength + 1);
			ds.readRawData(value, tag.valueLength);
			values.insert(tag.key, QString(value));
			delete[] value;
		}
		else if (tag.valueType == Tag::UINT)
		{
			uint value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::INT)
		{
			int value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::USHORT)
		{
			ushort value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::SHORT)
		{
			short value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::FLOAT)
		{
			float value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::DOUBLE)
		{
			double value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::UINT64)
		{
			quint64 value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		else if (tag.valueType == Tag::UINT8)
		{
			quint8 value = 0;
			ds >> value;
			values.insert(tag.key, value);
		}
		lastTagEnd = tag.end;
	}
	return values;
}

QString Renderer::render(const QString& _template, Context* context)
{
	m_error.clear();
	m_errorPos = -1;
	m_errorPartial.clear();

	m_tagStartMarker = m_defaultTagStartMarker;
	m_tagEndMarker = m_defaultTagEndMarker;

	return render(_template, 0, _template.length(), context);
}

QString Renderer::render(const QString& _template, int startPos, int endPos, Context* context)
{
	QString output;
	int lastTagEnd = startPos;

	while (m_errorPos == -1) {
		Tag tag = findTag(_template, lastTagEnd, endPos);
		if (tag.type == Tag::Null) {
			output += _template.midRef(lastTagEnd, endPos - lastTagEnd);
			break;
		}
		output += _template.midRef(lastTagEnd, tag.start - lastTagEnd);
		switch (tag.type) {
		case Tag::Value:
		{
			QString value = context->stringValue(tag.key);
			if (tag.escapeMode == Tag::Escape) {
				value = escapeHtml(value);
			} else if (tag.escapeMode == Tag::Unescape) {
				value = unescapeHtml(value);
			}
			output += value;
			lastTagEnd = tag.end;
		}
		break;
		case Tag::SectionStart:
		{
			Tag endTag = findEndTag(_template, tag, endPos);
			if (endTag.type == Tag::Null) {
				if (m_errorPos == -1) {
					setError("No matching end tag found for section", tag.start);
				}
			} else {
				int listCount = context->listCount(tag.key);
				if (listCount > 0) {
					for (int i=0; i < listCount; i++) {
						context->push(tag.key, i);
						output += render(_template, tag.end, endTag.start, context);
						context->pop();
					}
				} else if (context->canEval(tag.key)) {
					output += context->eval(tag.key, _template.mid(tag.end, endTag.start - tag.end), this);
				} else if (!context->isFalse(tag.key)) {
					context->push(tag.key);
					output += render(_template, tag.end, endTag.start, context);
					context->pop();
				}
				lastTagEnd = endTag.end;
			}
		}
		break;
		case Tag::InvertedSectionStart:
		{
			Tag endTag = findEndTag(_template, tag, endPos);
			if (endTag.type == Tag::Null) {
				if (m_errorPos == -1) {
					setError("No matching end tag found for inverted section", tag.start);
				}
			} else {
				if (context->isFalse(tag.key)) {
					output += render(_template, tag.end, endTag.start, context);
				}
				lastTagEnd = endTag.end;
			}
		}
		break;
		case Tag::SectionEnd:
			setError("Unexpected end tag", tag.start);
			lastTagEnd = tag.end;
			break;
		case Tag::Partial:
		{
			QString tagStartMarker = m_tagStartMarker;
			QString tagEndMarker = m_tagEndMarker;

			m_tagStartMarker = m_defaultTagStartMarker;
			m_tagEndMarker = m_defaultTagEndMarker;

			m_partialStack.push(tag.key);

			QString partialContent = context->partialValue(tag.key);

			// If there is a need to add a special indentation to the partial
			if (tag.indentation > 0) {
				output += QString(" ").repeated(tag.indentation);
				// Indenting the output to keep the parent indentation.
				int posOfLF = partialContent.indexOf("\n", 0);
				while (posOfLF > 0 && posOfLF < (partialContent.length() - 1)) { // .length() - 1 because we dont want indentation AFTER the last character if it's a LF
					partialContent = partialContent.insert(posOfLF + 1, QString(" ").repeated(tag.indentation));
					posOfLF = partialContent.indexOf("\n", posOfLF + 1);
				}
			}

			QString partialRendered = render(partialContent, 0, partialContent.length(), context);

			output += partialRendered;

			lastTagEnd = tag.end;

			m_partialStack.pop();

			m_tagStartMarker = tagStartMarker;
			m_tagEndMarker = tagEndMarker;
		}
		break;
		case Tag::SetDelimiter:
			lastTagEnd = tag.end;
			break;
		case Tag::Comment:
			lastTagEnd = tag.end;
			break;
		case Tag::Null:
			break;
		}
	}

	return output;
}

void Renderer::setError(const QString& error, int pos)
{
	Q_ASSERT(!error.isEmpty());
	Q_ASSERT(pos >= 0);

	m_error = error;
	m_errorPos = pos;

	if (!m_partialStack.isEmpty())
	{
		m_errorPartial = m_partialStack.top();
	}
}

Tag Renderer::findTag(const QString& content, int pos, int endPos)
{
	int tagStartPos = content.indexOf(m_tagStartMarker, pos);
	if (tagStartPos == -1 || tagStartPos >= endPos) {
		return Tag();
	}

	int tagEndPos = content.indexOf(m_tagEndMarker, tagStartPos + m_tagStartMarker.length());
	if (tagEndPos == -1) {
		return Tag();
	}
	tagEndPos += m_tagEndMarker.length();

	Tag tag;
	tag.type = Tag::Value;
	tag.start = tagStartPos;
	tag.end = tagEndPos;

	pos = tagStartPos + m_tagStartMarker.length();
	endPos = tagEndPos - m_tagEndMarker.length();

	QChar typeChar = content.at(pos);

	if (typeChar == '#') {
		tag.type = Tag::SectionStart;
		tag.key = readTagName(content, pos+1, endPos);
	} else if (typeChar == '^') {
		tag.type = Tag::InvertedSectionStart;
		tag.key = readTagName(content, pos+1, endPos);
	} else if (typeChar == '/') {
		tag.type = Tag::SectionEnd;
		tag.key = readTagName(content, pos+1, endPos);
	} else if (typeChar == '!') {
		tag.type = Tag::Comment;
	} else if (typeChar == '>') {
		tag.type = Tag::Partial;
		tag.key = readTagName(content, pos+1, endPos);
	} else if (typeChar == '=') {
		tag.type = Tag::SetDelimiter;
		readSetDelimiter(content, pos+1, tagEndPos - m_tagEndMarker.length());
	} else if (typeChar == '*') {
		tag.type = Tag::Input;
		tag.key = readTagName(content, pos + 1, endPos);
		tag.valueType = readTagValueType(content, pos, endPos);
	}
	else {
		if (typeChar == '&') {
			tag.escapeMode = Tag::Unescape;
			++pos;
		} else if (typeChar == '{') {
			tag.escapeMode = Tag::Raw;
			++pos;
			int endTache = content.indexOf('}', pos);
			if (endTache == tag.end - m_tagEndMarker.length()) {
				++tag.end;
			} else {
				endPos = endTache;
			}
		}
		tag.type = Tag::Value;
		tag.key = readTagName(content, pos, endPos);
		tag.valueType = readTagValueType(content, pos, endPos);			
		tag.value = readTagValue(content, pos, endPos);
	}

	if (tag.valueType == Tag::CHAR){
		tag.valueLength = readTagValueLength(content, pos, endPos);
	}

	if (tag.key.contains("-"))
	{
		QStringList sl = tag.key.split("-");
		tag.key = sl[0];
		tag.label = sl[1];
	}

	if (tag.type != Tag::Value) {
		expandTag(tag, content);
	}

	return tag;
}

QString Renderer::readTagName(const QString& content, int pos, int endPos)
{
	QString name;
	name.reserve(endPos - pos);
	while (content.at(pos).isSpace()) {
		++pos;
	}
	while (!content.at(pos).isSpace() && content.at(pos) != '|' && pos < endPos) {
		name += content.at(pos);
		++pos;
	}
	return name;
}

Tag::ValueType Renderer::readTagValueType(const QString& content, int pos, int endPos)
{
	if (pos >= content.length()) {
		return Tag::UNKNOW;
	}

	QString str;
	str.reserve(endPos - pos);	
	while (content.at(pos) != '|') {
		++pos;
	}

	while (!content.at(pos).isSpace() && content.at(pos) != ':' && content.at(pos) != '=' && pos < endPos) {
		str += content.at(pos);
		++pos;
	}
	
	if (str == "|char")
		return Tag::CHAR;
	else if (str == "|uint")
		return Tag::UINT;
	else if (str == "|int")
		return Tag::INT;
	else if (str == "|ushort")
		return Tag::USHORT;
	else if (str == "|short")
		return Tag::SHORT;
	else if (str == "|float")
		return Tag::FLOAT;
	else if (str == "|double")
		return Tag::DOUBLE;
	else if (str == "|str")
		return Tag::STR;
	else if (str == "|datetime")
		return Tag::DATETIME;
	else if (str == "|uint64")
		return Tag::UINT64;
	else if (str == "|uint8")
		return Tag::UINT8;

	return Tag::UNKNOW;
}

QVariant Renderer::readTagValue(const QString& content, int pos, int endPos)
{
	if (pos >= content.length()) {
		return QVariant();
	}

	QString str;
	str.reserve(endPos - pos);	
	while (content.at(pos) != '=' && pos < endPos) {
		++pos;
	}

	while (!content.at(pos).isSpace() && pos < endPos) {
		if (content.at(pos) != '=')
			str += content.at(pos);
		++pos;
	}
	
	return str;
}

quint16 Renderer::readTagValueLength(const QString& content, int pos, int endPos)
{
	QString str;
	str.reserve(endPos - pos);
	while (content.at(pos) != ':') {
		++pos;

		if (pos == endPos)
		{
			return 1;
		}
	}

	while (!content.at(pos).isSpace() &&pos < endPos) {
		if (content.at(pos) != ':')
			str += content.at(pos);

		++pos;
	}

	return str.toUInt();
}

void Renderer::readSetDelimiter(const QString& content, int pos, int endPos)
{
	QString startMarker;
	QString endMarker;

	while (content.at(pos).isSpace() && pos < endPos) {
		++pos;
	}

	while (!content.at(pos).isSpace() && pos < endPos) {
		if (content.at(pos) == '=') {
			setError("Custom delimiters may not contain '='.", pos);
			return;
		}
		startMarker += content.at(pos);
		++pos;
	}

	while (content.at(pos).isSpace() && pos < endPos) {
		++pos;
	}

	while (!content.at(pos).isSpace() && pos < endPos - 1) {
		if (content.at(pos) == '=') {
			setError("Custom delimiters may not contain '='.", pos);
			return;
		}
		endMarker += content.at(pos);
		++pos;
	}

	m_tagStartMarker = startMarker;
	m_tagEndMarker = endMarker;
}

Tag Renderer::findEndTag(const QString& content, const Tag& startTag, int endPos)
{
	int tagDepth = 1;
	int pos = startTag.end;

	while (true) {
		Tag nextTag = findTag(content, pos, endPos);
		if (nextTag.type == Tag::Null) {
			return nextTag;
		} else if (nextTag.type == Tag::SectionStart || nextTag.type == Tag::InvertedSectionStart) {
			++tagDepth;
		} else if (nextTag.type == Tag::SectionEnd) {
			--tagDepth;
			if (tagDepth == 0) {
				if (nextTag.key != startTag.key) {
					setError("Tag start/end key mismatch", nextTag.start);
					return Tag();
				}
				return nextTag;
			}
		}
		pos = nextTag.end;
	}

	return Tag();
}

void Renderer::setTagMarkers(const QString& startMarker, const QString& endMarker)
{
	m_defaultTagStartMarker = startMarker;
	m_defaultTagEndMarker = endMarker;
}

void Renderer::expandTag(Tag& tag, const QString& content)
{
	int start = tag.start;
	int end = tag.end;
	int indentation = 0;

	// Move start to beginning of line.
	while (start > 0 && content.at(start - 1) != QLatin1Char('\n')) {
		--start;
		if (!content.at(start).isSpace()) {
			return; // Not standalone.
		} else if (content.at(start).category() == QChar::Separator_Space) {
			// If its an actual "white space" and not a new line, it counts toward indentation.
			++indentation;
		}
	}

	// Move end to one past end of line.
	while (end <= content.size() && content.at(end - 1) != QLatin1Char('\n')) {
		if (end < content.size() && !content.at(end).isSpace()) {
			return; // Not standalone.
		}
		++end;
	}

	tag.start = start;
	tag.end = end;
	tag.indentation = indentation;
}
