/**
* @type {boolean} Whether to decode HTML entities when decoding text
*/
let decodeHtmlEntities = config.decodeHtmlEntities;

/**
* @type {boolean} Whether text contains escape characters
*/
let hasEscapedChars = false;

/**
* @type {boolean} Whether text contains link references
*/
let hasReferences = false;

/**
* @dict
*/
let linkReferences = {};

if (text.indexOf('\\') >= 0)
{
	hasEscapedChars = true;

	// Encode escaped literals that have a special meaning otherwise, so that we don't have
	// to take them into account in regexps
	text = text.replace(
		/\\[!"'()*<>[\\\]^_`~]/g,
		function (str)
		{
			return {
				'\\!': "\x1B0", '\\"' : "\x1B1", "\\'": "\x1B2", '\\(': "\x1B3",
				'\\)': "\x1B4", '\\*' : "\x1B5", '\\<': "\x1B6", '\\>': "\x1B7",
				'\\[': "\x1B8", '\\\\': "\x1B9", '\\]': "\x1BA", '\\^': "\x1BB",
				'\\_': "\x1BC", '\\`' : "\x1BD", '\\~': "\x1BE"
			}[str];
		}
	);
}

// We append a couple of lines and a non-whitespace character at the end of the text in
// order to trigger the closure of all open blocks such as quotes and lists
text += "\n\n\x17";

/**
* Decode a chunk of encoded text to be used as an attribute value
*
* Decodes escaped literals and removes slashes and 0x1A characters
*
* @param  {string} str Encoded text
* @return {string}     Decoded text
*/
function decode(str)
{
	if (HINT.LITEDOWN_DECODE_HTML_ENTITIES && decodeHtmlEntities && str.indexOf('&') > -1)
	{
		str = html_entity_decode(str);
	}
	str = str.replace(/\x1A/g, '');

	if (hasEscapedChars)
	{
		str = str.replace(
			/\x1B./g,
			function (seq)
			{
				return {
					"\x1B0": '!', "\x1B1": '"',  "\x1B2": "'", "\x1B3": '(',
					"\x1B4": ')', "\x1B5": '*',  "\x1B6": '<', "\x1B7": '>',
					"\x1B8": '[', "\x1B9": '\\', "\x1BA": ']', "\x1BB": '^',
					"\x1BC": '_', "\x1BD": '`',  "\x1BE": '~'
				}[seq];
			}
		);
	}

	return str;
}

/**
* Test whether given position is preceded by whitespace
*
* @param  {number}  pos
* @return {boolean}
*/
function isAfterWhitespace(pos)
{
	return (pos > 0 && isWhitespace(text.charAt(pos - 1)));
}

/**
* Test whether given character is alphanumeric
*
* @param  {string}  chr
* @return {boolean}
*/
function isAlnum(chr)
{
	return (' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.indexOf(chr) > 0);
}

/**
* Test whether given position is followed by whitespace
*
* @param  {number}  pos
* @return {boolean}
*/
function isBeforeWhitespace(pos)
{
	return isWhitespace(text[pos + 1]);
}

/**
* Test whether a length of text is surrounded by alphanumeric characters
*
* @param  {number}  pos Start of the text
* @param  {number}  len Length of the text
* @return {boolean}
*/
function isSurroundedByAlnum(pos, len)
{
	return (pos > 0 && isAlnum(text[pos - 1]) && isAlnum(text[pos + len]));
}

/**
* Test whether given character is an ASCII whitespace character
*
* NOTE: newlines are normalized to LF before parsing so we don't have to check for CR
*
* @param  {string}  chr
* @return {boolean}
*/
function isWhitespace(chr)
{
	return (" \n\t".indexOf(chr) > -1);
}

/**
* Mark the boundary of a block in the original text
*
* @param {number} pos
*/
function markBoundary(pos)
{
	text = text.substring(0, pos) + "\x17" + text.substring(pos + 1);
}

/**
* Overwrite part of the text with substitution characters ^Z (0x1A)
*
* @param  {number} pos Start of the range
* @param  {number} len Length of text to overwrite
*/
function overwrite(pos, len)
{
	if (len > 0)
	{
		text = text.substring(0, pos) + new Array(1 + len).join("\x1A") + text.substring(pos + len);
	}
}