/**
 * @fileoverview Disallows multiple blank lines.
 * implementation adapted from the no-trailing-spaces rule.
 * @author Greg Cochard
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        docs: {
            description: "disallow multiple empty lines",
            category: "Stylistic Issues",
            recommended: false
        },

        fixable: "whitespace",

        schema: [
            {
                type: "object",
                properties: {
                    max: {
                        type: "integer",
                        minimum: 0
                    },
                    maxEOF: {
                        type: "integer",
                        minimum: 0
                    },
                    maxBOF: {
                        type: "integer",
                        minimum: 0
                    }
                },
                required: ["max"],
                additionalProperties: false
            }
        ]
    },

    create(context) {

        // Use options.max or 2 as default
        let max = 2,
            maxEOF,
            maxBOF;

        // store lines that appear empty but really aren't
        const notEmpty = [];

        if (context.options.length) {
            max = context.options[0].max;
            maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
            maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
        }

        const sourceCode = context.getSourceCode();

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {

            TemplateLiteral(node) {
                let start = node.loc.start.line;
                const end = node.loc.end.line;

                while (start <= end) {
                    notEmpty.push(start);
                    start++;
                }
            },

            "Program:exit": function checkBlankLines(node) {
                const lines = sourceCode.lines,
                    fullLines = sourceCode.text.match(/.*(\r\n|\r|\n|\u2028|\u2029)/g) || [],
                    linesRangeStart = [];
                let firstNonBlankLine = -1,
                    trimmedLines = [],
                    blankCounter = 0,
                    currentLocation,
                    lastLocation,
                    firstOfEndingBlankLines,
                    diff,
                    rangeStart,
                    rangeEnd;

                /**
                 * Fix code.
                 * @param {RuleFixer} fixer - The fixer of this context.
                 * @returns {Object} The fixing information.
                 */
                function fix(fixer) {
                    return fixer.removeRange([rangeStart, rangeEnd]);
                }

                linesRangeStart.push(0);
                lines.forEach(function(str, i) {
                    const length = i < fullLines.length ? fullLines[i].length : 0,
                        trimmed = str.trim();

                    if ((firstNonBlankLine === -1) && (trimmed !== "")) {
                        firstNonBlankLine = i;
                    }

                    linesRangeStart.push(linesRangeStart[linesRangeStart.length - 1] + length);
                    trimmedLines.push(trimmed);
                });

                // add the notEmpty lines in there with a placeholder
                notEmpty.forEach(function(x, i) {
                    trimmedLines[i] = x;
                });

                if (typeof maxEOF === "undefined") {

                    /*
                     * Swallow the final newline, as some editors add it
                     * automatically and we don't want it to cause an issue
                     */
                    if (trimmedLines[trimmedLines.length - 1] === "") {
                        trimmedLines = trimmedLines.slice(0, -1);
                    }

                    firstOfEndingBlankLines = trimmedLines.length;
                } else {

                    // save the number of the first of the last blank lines
                    firstOfEndingBlankLines = trimmedLines.length;
                    while (trimmedLines[firstOfEndingBlankLines - 1] === ""
                            && firstOfEndingBlankLines > 0) {
                        firstOfEndingBlankLines--;
                    }
                }

                // Aggregate and count blank lines
                if (firstNonBlankLine > maxBOF) {
                    diff = firstNonBlankLine - maxBOF;
                    rangeStart = linesRangeStart[firstNonBlankLine - diff];
                    rangeEnd = linesRangeStart[firstNonBlankLine];
                    context.report({
                        node,
                        loc: node.loc.start,
                        message: "Too many blank lines at the beginning of file. Max of {{maxBOF}} allowed.",
                        data: {
                            maxBOF
                        },
                        fix
                    });
                }
                currentLocation = firstNonBlankLine - 1;

                lastLocation = currentLocation;
                currentLocation = trimmedLines.indexOf("", currentLocation + 1);
                while (currentLocation !== -1) {
                    lastLocation = currentLocation;
                    currentLocation = trimmedLines.indexOf("", currentLocation + 1);
                    if (lastLocation === currentLocation - 1) {
                        blankCounter++;
                    } else {
                        const location = {
                            line: lastLocation + 1,
                            column: 0
                        };

                        if (lastLocation < firstOfEndingBlankLines) {

                            // within the file, not at the end
                            if (blankCounter >= max) {
                                diff = blankCounter - max + 1;
                                rangeStart = linesRangeStart[location.line - diff];
                                rangeEnd = linesRangeStart[location.line];

                                context.report({
                                    node,
                                    loc: location,
                                    message: "More than {{max}} blank {{lines}} not allowed.",
                                    data: {
                                        max,
                                        lines: (max === 1 ? "line" : "lines")
                                    },
                                    fix
                                });
                            }
                        } else {

                            // inside the last blank lines
                            if (blankCounter > maxEOF) {
                                diff = blankCounter - maxEOF + 1;
                                rangeStart = linesRangeStart[location.line - diff];
                                rangeEnd = linesRangeStart[location.line - 1];
                                context.report({
                                    node,
                                    loc: location,
                                    message: "Too many blank lines at the end of file. Max of {{maxEOF}} allowed.",
                                    data: {
                                        maxEOF
                                    },
                                    fix
                                });
                            }
                        }

                        // Finally, reset the blank counter
                        blankCounter = 0;
                    }
                }
            }
        };

    }
};
