/**
 * @fileoverview A rule to suggest using template literals instead of string concatenation.
 * @author Toru Nagashima
 * @copyright 2015 Toru Nagashima. All rights reserved.
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Checks whether or not a given node is a concatenation.
 * @param {ASTNode} node - A node to check.
 * @returns {boolean} `true` if the node is a concatenation.
 */
function isConcatenation(node) {
    return node.type === "BinaryExpression" && node.operator === "+";
}

/**
 * Gets the top binary expression node for concatenation in parents of a given node.
 * @param {ASTNode} node - A node to get.
 * @returns {ASTNode} the top binary expression node in parents of a given node.
 */
function getTopConcatBinaryExpression(node) {
    while (isConcatenation(node.parent)) {
        node = node.parent;
    }
    return node;
}

/**
 * Checks whether or not a given binary expression has non string literals.
 * @param {ASTNode} node - A node to check.
 * @returns {boolean} `true` if the node has non string literals.
 */
function hasNonStringLiteral(node) {
    if (isConcatenation(node)) {
        // `left` is deeper than `right` normally.
        return hasNonStringLiteral(node.right) || hasNonStringLiteral(node.left);
    }
    return !astUtils.isStringLiteral(node);
}

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

module.exports = function(context) {
    var done = Object.create(null);

    /**
     * Reports if a given node is string concatenation with non string literals.
     *
     * @param {ASTNode} node - A node to check.
     * @returns {void}
     */
    function checkForStringConcat(node) {
        if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) {
            return;
        }

        var topBinaryExpr = getTopConcatBinaryExpression(node.parent);

        // Checks whether or not this node had been checked already.
        if (done[topBinaryExpr.range[0]]) {
            return;
        }
        done[topBinaryExpr.range[0]] = true;

        if (hasNonStringLiteral(topBinaryExpr)) {
            context.report(
                topBinaryExpr,
                "Unexpected string concatenation.");
        }
    }

    return {
        Program: function() {
            done = Object.create(null);
        },

        Literal: checkForStringConcat,
        TemplateLiteral: checkForStringConcat
    };
};

module.exports.schema = [];
