let pos, table = null, tableTag, tables, _text = text;

if (config.overwriteMarkdown)
{
	overwriteMarkdown();
}
if (config.overwriteEscapes)
{
	overwriteEscapes();
}

captureTables();
processTables();

/**
* Add current line to a table
*
* @param {string} line Line of text
*/
function addLine(line)
{
	let ignoreLen = 0;

	if (!table)
	{
		table = { rows: [] };

		// Make the table start at the first non-space character
		ignoreLen = /^ */.exec(line)[0].length;
		line      = line.substring(ignoreLen);
	}

	// Overwrite the outermost pipes
	line = line.replace(/^( *)\|/, '$1 ').replace(/\|( *)$/, ' $1');

	table.rows.push({ line: line, pos: pos + ignoreLen });
}

/**
* Process current table's body
*/
function addTableBody()
{
	let i   = 1,
		cnt = table.rows.length;
	while (++i < cnt)
	{
		addTableRow('TD', table.rows[i]);
	}

	createBodyTags(table.rows[2].pos, pos);
}

/**
* Add a cell's tags for current table at current position
*
* @param {string} tagName Either TD or TH
* @param {string} align   Either "left", "center", "right" or ""
* @param {string} content Cell's text content
*/
function addTableCell(tagName, align, content)
{
	let startPos  = pos,
		endPos    = startPos + content.length,
		ignoreLen;
	pos = endPos;

	let m = /^( *).*?( *)$/.exec(content);
	if (m[1])
	{
		ignoreLen = m[1].length;
		createIgnoreTag(startPos, ignoreLen);
		startPos += ignoreLen;
	}
	if (m[2])
	{
		ignoreLen = m[2].length;
		createIgnoreTag(endPos - ignoreLen, ignoreLen);
		endPos -= ignoreLen;
	}

	createCellTags(tagName, startPos, endPos, align);
}

/**
* Process current table's head
*/
function addTableHead()
{
	addTableRow('TH', table.rows[0]);
	createHeadTags(table.rows[0].pos, pos);
}

/**
* Process given table row
*
* @param {string}  tagName Either TD or TH
* @param {!Object} row
*/
function addTableRow(tagName, row)
{
	pos = row.pos;
	row.line.split('|').forEach((str, i) =>
	{
		if (i > 0)
		{
			createIgnoreTag(pos, 1);
			++pos;
		}

		let align = (!table.cols[i]) ? '' : table.cols[i];
		addTableCell(tagName, align, str);
	});

	createRowTags(row.pos, pos);
}

/**
* Capture all pipe tables in current text
*/
function captureTables()
{
	table  = null;
	tables = [];

	pos = 0;
	_text.split("\n").forEach((line) =>
	{
		if (line.indexOf('|') < 0)
		{
			endTable();
		}
		else
		{
			addLine(line);
		}
		pos += 1 + line.length;
	});
	endTable();
}

/**
* Create a pair of TBODY tags for given text span
*
* @param {number} startPos
* @param {number} endPos
*/
function createBodyTags(startPos, endPos)
{
	addTagPair('TBODY', startPos, 0, endPos, 0, -103);
}

/**
* Create a pair of TD or TH tags for given text span
*
* @param {string} tagName  Either TD or TH
* @param {number} startPos
* @param {number} endPos
* @param {string} align    Either "left", "center", "right" or ""
*/
function createCellTags(tagName, startPos, endPos, align)
{
	let tag;
	if (startPos === endPos)
	{
		tag = addSelfClosingTag(tagName, startPos, 0, -101);
	}
	else
	{
		tag = addTagPair(tagName, startPos, 0, endPos, 0, -101);
	}
	if (align)
	{
		tag.setAttribute('align', align);
	}
}

/**
* Create a pair of THEAD tags for given text span
*
* @param {number} startPos
* @param {number} endPos
*/
function createHeadTags(startPos, endPos)
{
	addTagPair('THEAD', startPos, 0, endPos, 0, -103);
}

/**
* Create an ignore tag for given text span
*
* @param {number} pos
* @param {number} len
*/
function createIgnoreTag(pos, len)
{
	tableTag.cascadeInvalidationTo(addIgnoreTag(pos, len, 1000));
}

/**
* Create a pair of TR tags for given text span
*
* @param {number} startPos
* @param {number} endPos
*/
function createRowTags(startPos, endPos)
{
	addTagPair('TR', startPos, 0, endPos, 0, -102);
}

/**
* Create an ignore tag for given separator row
*
* @param {!Object} row
*/
function createSeparatorTag(row)
{
	createIgnoreTag(row.pos - 1, 1 + row.line.length);
}

/**
* Create a pair of TABLE tags for given text span
*
* @param {number} startPos
* @param {number} endPos
*/
function createTableTags(startPos, endPos)
{
	tableTag = addTagPair('TABLE', startPos, 0, endPos, 0, -104);
}

/**
* End current buffered table
*/
function endTable()
{
	if (hasValidTable())
	{
		table.cols = parseColumnAlignments(table.rows[1].line);
		tables.push(table);
	}
	table = null;
}

/**
* Test whether a valid table is currently buffered
*
* @return {boolean}
*/
function hasValidTable()
{
	return (table && table.rows.length > 2 && isValidSeparator(table.rows[1].line));
}

/**
* Test whether given line is a valid separator
*
* @param  {string}  line
* @return {boolean}
*/
function isValidSeparator(line)
{
	return /^ *:?-+:?(?:(?:\+| *\| *):?-+:?)+ */.test(line);
}

/**
* Overwrite right angle brackets in given match
*
* @param  {string} str
* @return {string}
*/
function overwriteBlockquoteCallback(str)
{
	return str.replace(/[!>]/g, ' ');
}

/**
* Overwrite escape sequences in current text
*/
function overwriteEscapes()
{
	if (_text.indexOf('\\|') > -1)
	{
		_text = _text.replace(/\\[\\|]/g, '..');
	}
}

/**
* Overwrite backticks in given match
*
* @param  {string} str
* @return {string}
*/
function overwriteInlineCodeCallback(str)
{
	return str.replace(/\|/g, '.');
}

/**
* Overwrite Markdown-style markup in current text
*/
function overwriteMarkdown()
{
	// Overwrite inline code spans
	if (_text.indexOf('`') > -1)
	{
		_text = _text.replace(/`[^`]*`/g, overwriteInlineCodeCallback);
	}

	// Overwrite blockquotes
	if (_text.indexOf('>') > -1)
	{
		_text = _text.replace(/^(?:>!? ?)+/gm, overwriteBlockquoteCallback);
	}
}

/**
* Parse and return column alignments in given separator line
*
* @param  {string} line
* @return {!Array<string>}
*/
function parseColumnAlignments(line)
{
	// Use a bitfield to represent the colons' presence and map it to the CSS value
	let align = [
			'',
			'right',
			'left',
			'center'
		],
		cols = [],
		regexp = /(:?)-+(:?)/g,
		m;

	while (m = regexp.exec(line))
	{
		let key = (m[1] ? 2 : 0) + (m[2] ? 1 : 0);
		cols.push(align[key]);
	}

	return cols;
}

/**
* Process current table declaration
*/
function processCurrentTable()
{
	let firstRow = table.rows[0],
		lastRow  = table.rows[table.rows.length - 1];
	createTableTags(firstRow.pos, lastRow.pos + lastRow.line.length);

	addTableHead();
	createSeparatorTag(table.rows[1]);
	addTableBody();
}

/**
* Process all the captured tables
*/
function processTables()
{
	let i = -1, cnt = tables.length;
	while (++i < cnt)
	{
		table = tables[i];
		processCurrentTable();
	}
}