'use strict';

module.exports.makeError = function (message, filename, line)
{
    message = (message || 'Unknown error') + ' (' + filename + ':' + line + ')';
    return new Error(message, filename, line);
};

/*
See index.js for documentation explaining what this can parse
*/
module.exports.parse = function parse(text, filename)
{
    var REX_TAG_START = /<!--\s*#include\s*/gmi,
        REX_ATTR_NAME_START = /\S/gm, //regex to find non-whitespace char
        REX_ATTR_NAME_END = /[=\s]/gm, //regex which finds end of attribute name
        REX_ATTR_VALUE_UNQUOTED_END = /\s/gm, //regex which finds end of attribute name
        match, //bin for regex matches
        chunks = [],
        index = 0, //scan index
        attrs, //k/v pairs in each #include
        attrName; //current attribute's name

    while (index <= text.length)
    {
        //find the next open-tag with a regex
        REX_TAG_START.lastIndex = index;
        match = REX_TAG_START.exec(text);
        //no other directives left, so we're done
        if (!match)
        {
            //make sure we add the chunk of text at the end
            if (index < text.length)
            {
                chunks.push(text.substring(index));
            }
            //we're free!
            break;
        }
        //pass through any text after the last match, but before this match
        if (match.index > index)
        {
            chunks.push(text.substring(index, match.index));
        }
        var includeStart = match.index;
        index = REX_TAG_START.lastIndex;
        //consume the tag and build k/v pairs
        attrs = {};
        while (index <= text.length)
        {
            //scan to next non-whitespace char
            REX_ATTR_NAME_START.lastIndex = index;
            match = REX_ATTR_NAME_START.exec(text);
            //didn't find one?
            if (!match)
            {
                throw err('Unexpected end of input (attr name)', index);
            }
            index = match.index;
            //are we at the end of the tag?
            if (text.substring(index, index + 3) === '-->')
            {
                index += 3;
                break;
            }
            //find the end of the attribute's name
            REX_ATTR_NAME_END.lastIndex = index;
            match = REX_ATTR_NAME_END.exec(text);
            if (!match)
            {
                throw err('Unexpected end of input (in attr name)', index);
            }
            attrName = text.substring(index, match.index);
            index = match.index + 1;
            //attribute with no value
            if (match[0] !== '=')
            {
                //TODO: Is this too strict? Requires no whitespace between attr name and equal sign
                //i.e.,  foo = "bar" will be parsed wrong, but is that OK with us?
                attrs[attrName] = true; //TODO: right representation?
                continue;
            }
            //unquoted attribute value
            //TODO: should we support single-quoted values too? meh
            if (text.substring(index, index + 1) !== '"')
            {
                REX_ATTR_VALUE_UNQUOTED_END.lastIndex = index;
                match = REX_ATTR_VALUE_UNQUOTED_END.exec(text);
                if (!match)
                {
                    throw err('Unexpected end of input (unquoted value)', index);
                }
                attrs[attrName] = text.substring(index, match.index);
                index = match.index;
                continue;
            }
            //quoted attribute value
            var quotedStart = index,
                escaping = false,
                charAtIndex = null;
            //find the end of the quoted string
            for (index = quotedStart + 1; index <= text.length; ++index)
            {
                charAtIndex = text.substring(index, index + 1);
                if (charAtIndex === '') //end of input
                {
                    throw err('Unexpected end of input (unterminated quoted value)', quotedStart);
                }
                else if (escaping) //this char is being skipped
                {
                    escaping = false;
                }
                else if (charAtIndex === '\\') //skip next char
                {
                    escaping = true;
                }
                else if (charAtIndex === '"') //found end of quoted string
                {
                    break;
                }
            }
            //move index after the quote
            index += 1;
            //extract the quoted string
            attrs[attrName] = JSON.parse(text.substring(quotedStart, index));
        }
        //validate that there's a file attribute
        if (!attrs.file)
        {
            throw err('Missing file= attribute in #include', includeStart);
        }

        var chunk = {
            type: 'include',
            file: attrs.file,
            line: indexToLine(text, includeStart),
            vars: attrs
        };
        delete attrs.file;
        chunks.push(chunk);
    }
    return chunks;

    //helper method
    function err(message, index)
    {
        var line = indexToLine(text, index);
        return module.exports.makeError(message, filename, line);
    }
};

function indexToLine(text, index)
{
    if (index > text.length)
    {
        index = text.length;
    }
    var line = 1,
        lineIndex = 0,
        nextLineIndex;
    while (true)
    {
        nextLineIndex = text.indexOf('\n', lineIndex);
        if (nextLineIndex === -1)
        {
            break;
        }
        if (nextLineIndex > index)
        {
            break;
        }
        //we are on next line
        line += 1;
        lineIndex = nextLineIndex + 1;
    }
    //var col = (index - lineIndex + 1);
    return line;
}
