// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

package org.jetbrains.kotlin.js.backend;

import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.JsExpressionStatement;
import org.jetbrains.annotations.NotNull;

/**
 * Determines if an expression statement needs to be surrounded by parentheses.
 * <p/>
 * The statement or the left-most expression needs to be surrounded by
 * parentheses if the left-most expression is an object literal or a function
 * object. Function declarations do not need parentheses.
 * <p/>
 * For example the following require parentheses:<br>
 * <ul>
 * <li>{ key : 'value'}</li>
 * <li>{ key : 'value'}.key</li>
 * <li>function () {return 1;}()</li>
 * <li>function () {return 1;}.prototype</li>
 * </ul>
 * <p/>
 * The following do not require parentheses:<br>
 * <ul>
 * <li>var x = { key : 'value'}</li>
 * <li>"string" + { key : 'value'}.key</li>
 * <li>function func() {}</li>
 * <li>function() {}</li>
 * </ul>
 */
public class JsFirstExpressionVisitor extends RecursiveJsVisitor {
    public static boolean exec(JsExpressionStatement statement) {
        JsExpression expression = statement.getExpression();
        // Pure function declarations do not need parentheses
        if (expression instanceof JsFunction || expression instanceof JsClass) {
            return false;
        }

        JsFirstExpressionVisitor visitor = new JsFirstExpressionVisitor();
        visitor.accept(statement.getExpression());
        return visitor.needsParentheses;
    }

    private boolean needsParentheses = false;

    private JsFirstExpressionVisitor() {
    }

    @Override
    public void visitArrayAccess(@NotNull JsArrayAccess x) {
        accept(x.getArrayExpression());
    }

    @Override
    public void visitArray(@NotNull JsArrayLiteral x) {
    }

    @Override
    public void visitBinaryExpression(@NotNull JsBinaryOperation x) {
        accept(x.getArg1());
    }

    @Override
    public void visitConditional(@NotNull JsConditional x) {
        accept(x.getTestExpression());
    }

    @Override
    public void visitFunction(@NotNull JsFunction x) {
        needsParentheses = true;
    }

    @Override
    public void visitInvocation(@NotNull JsInvocation invocation) {
        accept(invocation.getQualifier());
    }

    @Override
    public void visitNameRef(@NotNull JsNameRef nameRef) {
        if (!nameRef.isLeaf()) {
            accept(nameRef.getQualifier());
        }
    }

    @Override
    public void visitNew(@NotNull JsNew x) {
    }

    @Override
    public void visitObjectLiteral(@NotNull JsObjectLiteral x) {
        needsParentheses = true;
    }

    @Override
    public void visitPostfixOperation(@NotNull JsPostfixOperation x) {
        accept(x.getArg());
    }

    @Override
    public void visitPrefixOperation(@NotNull JsPrefixOperation x) {
    }
}
