/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * 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 com.google.javascript.jscomp;

import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node;
import java.util.HashMap;
import java.util.Map;

/**
 * Ensures that '@constructor X' has a 'goog.provide("X")' .
 */
class CheckProvides implements HotSwapCompilerPass {
  private final AbstractCompiler compiler;
  private final CodingConvention codingConvention;

  static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.warning(
      "JSC_MISSING_PROVIDE",
      "missing goog.provide(''{0}'')");

  CheckProvides(AbstractCompiler compiler) {
    this.compiler = compiler;
    this.codingConvention = compiler.getCodingConvention();
  }

  @Override
  public void process(Node externs, Node root) {
    hotSwapScript(root, null);
  }

  @Override
  public void hotSwapScript(Node scriptRoot, Node originalRoot) {
    CheckProvidesCallback callback =
        new CheckProvidesCallback(codingConvention);
    NodeTraversal.traverse(compiler, scriptRoot, callback);
  }

  private class CheckProvidesCallback implements Callback {
    private final Map<String, Node> provides = new HashMap<>();
    private final Map<String, Node> ctors = new HashMap<>();
    private final CodingConvention convention;
    private boolean containsRequires = false;

    CheckProvidesCallback(CodingConvention convention){
      this.convention = convention;
    }

    @Override
    public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
      // We do want to traverse the name of a named function, but we don't
      // want to traverse the arguments or body. We also don't care about modules.
      if (n.isModuleBody()) {
        return false;
      }
      return parent == null || !parent.isFunction() || n == parent.getFirstChild();
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      switch (n.getToken()) {
        case CALL:
          String providedClassName =
            codingConvention.extractClassNameIfProvide(n, parent);
          if (providedClassName != null) {
            provides.put(providedClassName, n);
          }
          if (!containsRequires && codingConvention.extractClassNameIfRequire(n, parent) != null) {
            containsRequires = true;
          }
          break;
        case FUNCTION:
          // Arrow function can't be constructors
          if (!n.isArrowFunction()) {
            visitFunctionNode(n, parent);
          }
          break;
        case CLASS:
          visitClassNode(n);
          break;
        case SCRIPT:
          visitScriptNode();
          break;
        default:
          break;
      }
    }

    private void visitFunctionNode(Node n, Node parent) {
      // TODO(user): Use isPrivate method below to recognize all functions.
      Node name = null;
      JSDocInfo info = parent.getJSDocInfo();
      if (info != null && info.isConstructor()) {
        name = parent.getFirstChild();
      } else {
        // look to the child, maybe it's a named function
        info = n.getJSDocInfo();
        if (info != null && info.isConstructor()) {
          name = n.getFirstChild();
        }
      }
      if (name != null && name.isQualifiedName()) {
        String qualifiedName = name.getQualifiedName();
        if (!this.convention.isPrivate(qualifiedName)) {
          Visibility visibility = info.getVisibility();
          if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
            ctors.put(qualifiedName, name);
          }
        }
      }
    }

    private void visitClassNode(Node classNode) {
      String name = NodeUtil.getName(classNode);
      if (name != null && !isPrivate(classNode)) {
        ctors.put(name, classNode);
      }
    }

    private boolean isPrivate(Node classOrFn) {
      JSDocInfo info = NodeUtil.getBestJSDocInfo(classOrFn);
      if (info != null && info.getVisibility().equals(JSDocInfo.Visibility.PRIVATE)) {
        return true;
      }
      return compiler.getCodingConvention().isPrivate(NodeUtil.getName(classOrFn));
    }

    private void visitScriptNode() {
      for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
        String ctorName = ctorEntry.getKey();
        int index = -1;
        boolean found = false;

        if (ctorName.startsWith("$jscomp.")
            || ClosureRewriteModule.isModuleContent(ctorName)
            || ClosureRewriteModule.isModuleExport(ctorName)) {
          continue;
        }

        do {
          index = ctorName.indexOf('.', index + 1);
          String provideKey = index == -1 ? ctorName : ctorName.substring(0, index);
          if (provides.containsKey(provideKey)) {
            found = true;
            break;
          }
        } while (index != -1);

        if (!found && (containsRequires || !provides.isEmpty())) {
          Node n = ctorEntry.getValue();
          compiler.report(
              JSError.make(n, MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
        }
      }
      provides.clear();
      ctors.clear();
      containsRequires = false;
    }
  }
}

@@ -1616,24 +1616,16 @@
 llback =
-%0A       
  new Che
@@ -1643,32 +1643,16 @@
 allback(
-codingConvention
 );%0A    N
@@ -1900,55 +1900,8 @@
 ();%0A
-    private final CodingConvention convention;%0A
     
@@ -1973,78 +1973,11 @@
 ack(
-CodingConvention convention)%7B%0A      this.convention = convention;%0A    
+) %7B
 %7D%0A%0A 
@@ -2047,21 +2047,9 @@
 sal 
-nodeTraversal
+t
 , No
@@ -3857,67 +3857,8 @@
 ();%0A
-        if (!this.convention.isPrivate(qualifiedName)) %7B%0A  
     
@@ -3903,26 +3903,24 @@
 sibility();%0A
-  
         if (
@@ -3977,26 +3977,24 @@
 %7B%0A          
- 
- 
 ctors.put(qu
@@ -4013,28 +4013,16 @@
  name);%0A
-          %7D%0A
         
@@ -4346,34 +4346,38 @@
 assOrFn);%0A      
-if
+return
  (info != null &
@@ -4440,122 +4440,8 @@
 TE))
- %7B%0A        return true;%0A      %7D%0A      return compiler.getCodingConvention().isPrivate(NodeUtil.getName(classOrFn))
 ;%0A  

Remove support for "private" by convention properties in CheckProvides. Since "by convention" private is not enforced elsewhere it doesn't make sense to enforce it here.