/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.js.testOld.utils;

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

import java.util.*;

public class CallCounter extends RecursiveJsVisitor {

    private final List<JsNameRef> callsNameRefs = new ArrayList<>();

    @NotNull
    private final Set<String> exceptFunctionNames;

    @NotNull
    private final Set<String> exceptScopes;

    private int excludedScopeOccurrenceCount;

    @NotNull
    public static CallCounter countCalls(@NotNull JsNode node) {
        return countCalls(node, Collections.emptySet());
    }

    @NotNull
    public static CallCounter countCalls(@NotNull JsNode node, @NotNull Set<String> exceptFunctionNames) {
        CallCounter visitor = new CallCounter(new HashSet<>(exceptFunctionNames), Collections.emptySet());
        node.accept(visitor);

        return visitor;
    }

    @NotNull
    public static CallCounter countCallsWithExcludedScopes(@NotNull JsNode node, @NotNull Set<String> exceptScopes) {
        CallCounter visitor = new CallCounter(Collections.emptySet(), new HashSet<>(exceptScopes));
        node.accept(visitor);

        return visitor;
    }

    private CallCounter(@NotNull Set<String> exceptFunctionNames, @NotNull Set<String> exceptScopes) {
        this.exceptFunctionNames = exceptFunctionNames;
        this.exceptScopes = exceptScopes;
    }

    public int getTotalCallsCount() {
        return callsNameRefs.size();
    }

    public int getQualifiedCallsCount(String... qualifiers) {
        int count = 0;
        List<String> expectedQualifierChain = new ArrayList<>();
        Collections.addAll(expectedQualifierChain, qualifiers);

        for (JsNameRef callNameRef : callsNameRefs) {
            if (matchesQualifiers(callNameRef, expectedQualifierChain)) {
                count++;
            }
        }

        return count;
    }

    public int getUnqualifiedCallsCount(String expectedName) {
        int count = 0;

        for (JsNameRef callNameRef : callsNameRefs) {
            String name = callNameRef.getIdent();
            if (name.equals(expectedName)) {
                count++;
            }
        }

        return count;
    }

    @Override
    public void visitInvocation(@NotNull JsInvocation invocation) {
        super.visitInvocation(invocation);
        JsExpression qualifier = invocation.getQualifier();

        if (qualifier instanceof JsNameRef) {
            JsNameRef nameRef = (JsNameRef) qualifier;
            if (!exceptFunctionNames.contains(nameRef.getIdent())) {
                callsNameRefs.add(nameRef);
            }
        }
    }

    @Override
    public void visitFunction(@NotNull JsFunction x) {
        if (x.getName() != null && exceptScopes.contains(x.getName().getIdent())) {
            excludedScopeOccurrenceCount++;
            return;
        }
        super.visitFunction(x);
    }

    @Override
    public void visitVars(@NotNull JsVars x) {
        for (JsVars.JsVar jsVar : x.getVars()) {
            if (jsVar.getInitExpression() == null) continue;
            if (!exceptScopes.contains(jsVar.getName().getIdent())) {
                accept(jsVar.getInitExpression());
            }
            else {
                excludedScopeOccurrenceCount++;
            }
        }
    }

    private static boolean matchesQualifiers(JsNameRef nameRef, List<String> expectedQualifierChain) {
        JsExpression currentQualifier = nameRef;

        for (String expectedQualifier : expectedQualifierChain) {
            if (!(currentQualifier instanceof JsNameRef)) {
                return false;
            }

            JsNameRef currentNameRef = (JsNameRef) currentQualifier;
            String name = currentNameRef.getIdent();
            if (!name.equals(expectedQualifier)) {
                return false;
            }

            currentQualifier = currentNameRef.getQualifier();
        }

        return true;
    }

    public int getExcludedScopeOccurrenceCount() {
        return excludedScopeOccurrenceCount;
    }
}
