function SermonFileWikiParser(strDefaultLanguage)
{
	this.strDefaultLanguage = strDefaultLanguage;
}

SermonFileWikiParser.prototype.ParseText = function(strText, objWriter)
{
	// regular expresion for run type.
	var reLineType = new RegExp(
		[
			"(?:^(!+)\\s+(.*)$)", // 1 - heading syntax, 2 - heading content
			"(?:^(\\{\\{\\{(?:\"|&quot;))$)", // 1 - blockquote start
			"(?:^(\\}\\}\\})$)", // 1 - blockquote end
			"(?:^(\\-{4,})\\s*$)", // 1 - Horizontal Rule syntax
			"(?:^(\\s*)(?:((?:\\*)|(?:\\#\\.))\\s)(.*)$)", // 1 - Indent level, 2 - List Type, 3 - List item contents
			"(?:^\\|\\s(.*)\\|\\s*$)", // 1 - Table Row content
			"(?:^(.*)$)" // 1 - content of the line that did not match anything else.
		].join("|"),
		"g");

	// Data used to keep track of line changes.
	var objLastLineData = {
		LineType: "",
		Close: null
	};

	var astrLines = strText.split(/\r?\n/);
	for (var nLine = 0; nLine < astrLines.length; ++nLine)
	{
		var objCurrentLineData = {
			LineType: "",
			Close: null
		};

		// data about the current line
		var strCurrentLine = astrLines[nLine];

		var aMatch = [];
		while ((aMatch = reLineType.exec(strCurrentLine)) != null && aMatch[0].length > 0)
		{
			// start with group 1 since 0 is all matches.
			var nGroupIndex = 1;

			// determine line type
			do
			{
				if (aMatch[0] && aMatch[0].length == 0)
				{
					objCurrentLineData.LineType = "EmptyLine";
					break;
				}

				// heading match
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "Heading";
					break;
				}
				nGroupIndex += 2;

				// Blockquote Start
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "BlockquoteStart";
					break;
				}
				nGroupIndex += 1;

				// Blockquote End
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "BlockquoteEnd";
					break;
				}
				nGroupIndex += 1;

				// Horizontal Rule
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "HorizontalRule";
					break;
				}
				nGroupIndex += 1;

				// List
				if (aMatch[nGroupIndex + 1] && aMatch[nGroupIndex + 1].length > 0)
				{
					objCurrentLineData.LineType = "List";
					break;
				}
				nGroupIndex += 3;

				// Table Row
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "TableRow";
					break;
				}
				nGroupIndex += 1;

				// Paragraph
				if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
				{
					objCurrentLineData.LineType = "Paragraph";
					break;
				}
				nGroupIndex += 1;
			}
			while (false);

			// close last line if we had a line type change
			if (objLastLineData.LineType != objCurrentLineData.LineType && objLastLineData.Close != null)
			{
				objLastLineData.Close();
				objLastLineData.Close = null;
			}

			// process this line
			switch (objCurrentLineData.LineType)
			{
				case "Heading":
					var nHeadingLevel = aMatch[nGroupIndex].length;
					objWriter.StartHeading(nHeadingLevel);
					this.ParseRuns(aMatch[nGroupIndex + 1], objWriter);
					objWriter.EndHeading(nHeadingLevel);
					break;

				case "HorizontalRule":
					objWriter.WriteHorizontalRule();
					break;

				case "List":
					objCurrentLineData = this.WriteListLine(objWriter, aMatch.slice(nGroupIndex, nGroupIndex + 3), objLastLineData);
					objCurrentLineData.LineType = "List";
					break;

				case "TableRow":
					objCurrentLineData = this.WriteTableRow(objWriter, aMatch.slice(nGroupIndex, nGroupIndex + 1), objLastLineData);
					objCurrentLineData.LineType = "TableRow";
					break;

				case "BlockquoteStart":
					objWriter.StartBlockquote();
					break;

				case "BlockquoteEnd":
					objWriter.EndBlockquote();
					break;

				case "Paragraph":
					objWriter.StartParagraph(objLastLineData.LineType == "Paragraph" ? "" : "First");
					this.ParseRuns(aMatch[nGroupIndex], objWriter);
					objWriter.EndParagraph();
					break;
			}

			// Make sure the line type is set to the correct information.
			objLastLineData = objCurrentLineData;
		}
	}
};

SermonFileWikiParser.prototype.WriteListLine = function(objWriter, astrData, objLastLineData)
{
	// create data for this line
	var objLineData = {
		Indent: astrData[0].length,
		Start: null,
		CloseThis: null,
		Close: function()
		{
			this.CloseThis();

			if (this.Parent != null)
				this.Parent.Close();
		},
		Parent: null,
		ListType: astrData[1] == "*" ? "Unordered" : "Ordered"
	};

	// determine the approprieate start and close
	switch (objLineData.ListType)
	{
		case "Unordered":
			objLineData.Start = function() { objWriter.StartUnorderedList(); };
			objLineData.CloseThis = function() { objWriter.EndUnorderedList(); };
			break;
		case "Ordered":
			objLineData.Start = function() { objWriter.StartOrderedList(); };
			objLineData.CloseThis = function() { objWriter.EndOrderedList(); };
			break;
	}

	// determine the parent and close any old lists.
	var objParent = null;
	var bStartNewList = false;
	if (objLineData.Indent < objLastLineData.Indent)
	{
		// close everything on the stack that has a greater indent than our current indent.
		objParent = objLastLineData;
		while (objParent != null && objLineData.Indent <= objParent.Indent)
		{
			if (objLineData.Indent != objParent.Indent)
			{
				objParent.CloseThis();
			}
			else if (objLineData.Indent == objParent.Indent && objParent.ListType != objLineData.ListType)
			{
				objParent.CloseThis();
				bStartNewList = true;
			}

			objParent = objParent.Parent;
		}
	}
	else if (objLineData.Indent == objLastLineData.Indent)
	{
		if (objLastLineData.ListType != objLineData.ListType)
			objLastLineData.CloseThis();

		objParent = objLastLineData.Parent;
	}
	else if (objLineData.Indent > objLastLineData.Indent)
	{
		objParent = objLastLineData;
	}

	// start any that need to be started
	if (bStartNewList ||
		objLastLineData.LineType != "List" ||
		objLineData.Indent > objLastLineData.Indent ||
		(objLineData.Indent == objLastLineData.Indent && objLastLineData.ListType != objLineData.ListType))
	{
		objLineData.Start();
	}

	// save the parent data
	objLineData.Parent = objParent;

	// write the actuall list item and its contents.
	objWriter.StartListItem();
	this.ParseRuns(astrData[2], objWriter);
	objWriter.EndListItem();

	return objLineData;
};

SermonFileWikiParser.prototype.WriteTableRow = function(objWriter, astrData, objLastLineData)
{
	var objLineData = {
		Close: function()
		{
			objWriter.EndTableRow();
			objWriter.EndTable();
		}
	};

	if (objLastLineData.LineType != "TableRow")
		objWriter.StartTable();

	objWriter.StartTableRow();

	var strRowData = astrData[0];
	var astrCells = strRowData.split(/\|/);

	for (var nCell = 0; nCell < astrCells.length; ++nCell)
	{
		objWriter.StartTableCell();
		this.ParseRuns(astrCells[nCell], objWriter);
		objWriter.EndTableCell();
	}

	return objLineData;
};

SermonFileWikiParser.prototype.ParseRuns = function(strText, objWriter)
{
	// regular expresion for run detection.
	var regRuns = new RegExp(
		[
			"(?:(?:~(\\S)))", // 1 - Escaped content
			"(?:([*/])(.*?)\\2)", // 1 - markup syntax, 2 - marked-up content
			"(?:\\[\\[(.*?)\\]\\])", // 1 - Link Content
			"(?:\\{\\{(.*?)\\}\\})", // 1 - Segment Content
			"(?:([\\u0590-\\u05ff]+))", // 1 - Hebrew Words
			"(?:([\\u0370-\\u03FF\\u1F00-\\u1FFF\\u0300-\\u036F]+))", // 1 - Greek Words
			"(?:(\\s\\\\\\\\\\s))" // 1 - line break syntax
		].join("|"),
		"g");

	var aMatch = [];
	var nNonMarkupIndex = 0;

	while ((aMatch = regRuns.exec(strText)) != null && aMatch[0].length > 0)
	{
		// write any non marked up content that occured before this match and the last one.
		this.ParseBibleReferences(strText.substr(nNonMarkupIndex, aMatch.index - nNonMarkupIndex), this.strDefaultLanguage, objWriter);

		// start with group 1 since 0 is all matches.
		var nGroupIndex = 1;

		// Escape markup
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			this.ParseBibleReferences(aMatch[nGroupIndex], this.strDefaultLanguage, objWriter);
		}
		nGroupIndex += 1;

		// Emphasis markup
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			switch (aMatch[nGroupIndex])
			{
				case "*": // bold
					objWriter.StartBold();
					this.ParseRuns(aMatch[nGroupIndex + 1], objWriter);
					objWriter.EndBold();
					break;
				case "/": // italics
					objWriter.StartItalics();
					this.ParseRuns(aMatch[nGroupIndex + 1], objWriter);
					objWriter.EndItalics();
					break;
			}
		}
		nGroupIndex += 2;

		var astrData;
		// Link markup
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			astrData = aMatch[nGroupIndex].match(/(?:(.*?)\|)?(?:(.*?)\:)?(.*)/); // 1 - Surface Text, 2 - Data Type, 3 - Data

			var strDataType = astrData[2];
			var strData = astrData[3];

			// get the data type reference
			var strDataTypeReference = strData;

			// get the surface text
			var strSurface = astrData[1];

			objWriter.WriteLink(strSurface, strDataTypeReference, strDataType);
		}
		nGroupIndex += 1;

		// Segment markup
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			astrData = aMatch[nGroupIndex].match(/^(?:(?:(?:\:(\S+?))|(?:\@(\S+?))))+(?:\s)(.*)/); // 1 - Language, 2 - Style, 3 - Content

			var strLanguage = this.strDefaultLanguage;
			var strStyle = "";

			if (astrData != null)
			{
				if (astrData[1] && astrData[1].length > 0)
					strLanguage = astrData[1];

				strStyle = astrData[2];

				objWriter.StartSegment(strLanguage, strStyle);
				this.ParseBibleReferences(astrData[3], strLanguage, objWriter);
				objWriter.EndSegment();
			}
		}
		nGroupIndex += 1;

		// Hebrew Text
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			objWriter.StartSegment("he", "");
			this.ParseBibleReferences(aMatch[nGroupIndex], "he", objWriter);
			objWriter.EndSegment();
		}
		nGroupIndex += 1;

		// Greek Text
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			objWriter.StartSegment("el", "");
			this.ParseBibleReferences(aMatch[nGroupIndex], "el", objWriter);
			objWriter.EndSegment();
		}
		nGroupIndex += 1;

		// Line Break
		if (aMatch[nGroupIndex] && aMatch[nGroupIndex].length > 0)
		{
			objWriter.WriteLineBreak();
		}
		nGroupIndex += 1;

		nNonMarkupIndex = regRuns.lastIndex;
	}

	// write any remaing text that is not marked
	this.ParseBibleReferences(strText.substr(nNonMarkupIndex), this.strDefaultLanguage, objWriter);
};

SermonFileWikiParser.prototype.ParseBibleReferences = function(strText, strLanguage, objWriter)
{
	/*
	// scan the text for bible references
	var objResults = Application.DataTypeManager.ScanText("bible", strText, strLanguage);

	var strProcessedText = "";
	var nLastProcessedIndex = 0;
	
	// walk the results from the scan
	for (var nResult = 0; nResult < objResults.Count; ++nResult)
	{
	var objItem = objResults.Item(nResult);

	// add text between last result and this one.
	objWriter.WriteText(strText.slice(nLastProcessedIndex, objItem.Offset));
		
	// add this result
	objWriter.WriteLink(strText.slice(objItem.Offset, objItem.Offset + objItem.Length), objItem.Reference);

	nLastProcessedIndex = objItem.Offset + objItem.Length;
	}
	*/
	// add any remaining text
	objWriter.WriteText(strText);
};