/*
 * Copyright 2020 Google Inc.
 *
 * 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.template.soy.passes;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.passes.CompilerFileSetPass.Result;
import com.google.template.soy.soytree.ImportNode;
import com.google.template.soy.soytree.ImportNode.ImportType;
import com.google.template.soy.soytree.ImportsContext.ImportsTemplateRegistry;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.TemplateNameRegistry;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.soytree.TemplatesPerFile;
import com.google.template.soy.soytree.TemplatesPerFile.TemplateName;
import com.google.template.soy.soytree.defn.ImportedVar;

/**
 * Resolves Soy template imports; verifies that the imports are valid and populates a local template
 * registry that maps the imported symbols to their types.
 */
@RunBefore({
  ResolveTemplateNamesPass.class,
})
public final class ResolveTemplateImportsPass extends ImportsPass implements CompilerFileSetPass {
  private TemplateNameRegistry templateNameRegistry;
  private final ErrorReporter errorReporter;

  ResolveTemplateImportsPass(ErrorReporter errorReporter) {
    this.templateNameRegistry = null;
    this.errorReporter = errorReporter;
  }

  @Override
  public Result run(
      ImmutableList<SoyFileNode> sourceFiles,
      IdGenerator nodeIdGen,
      TemplateNameRegistry templateNameRegistry,
      TemplateRegistry unusedPartialTemplateRegistry) {
    this.templateNameRegistry = templateNameRegistry;
    for (SoyFileNode sourceFile : sourceFiles) {
      visitFile(sourceFile);
    }
    return Result.CONTINUE;
  }

  ErrorReporter errorReporter() {
    return errorReporter;
  }

  @Override
  TemplateImportVisitor createImportVisitorForFile(SoyFileNode file) {
    return new TemplateImportVisitor(file, templateNameRegistry, errorReporter);
  }

  static final class TemplateImportVisitor extends ImportVisitor {
    // Names of the templates in each file (a lightweight template registry, without the metadata).
    private final TemplateNameRegistry templateNameRegistry;

    // Map of imported symbols to full template names.
    final ImmutableMap.Builder<String, TemplateName> symbolsToTemplatesMap =
        new ImmutableMap.Builder<>();

    TemplateImportVisitor(
        SoyFileNode file, TemplateNameRegistry templateNameRegistry, ErrorReporter errorReporter) {
      super(file, ImmutableSet.of(ImportType.TEMPLATE), errorReporter);
      this.templateNameRegistry = templateNameRegistry;
    }

    /**
     * Visits a template import node: verifies that the template names are valid and stores a
     * mapping from the imported symbol to the template info. Note that this is only called after
     * we've verified that the import path exists and any alias symbols are valid.
     */
    @Override
    void visitImportNodeWithValidPathAndSymbol(ImportNode node) {
      TemplatesPerFile templatesPerFile = templateNameRegistry.getTemplatesForFile(node.getPath());
      for (ImportedVar symbol : node.getIdentifiers()) {
        String name = symbol.name();
        // Report an error if the template name is invalid.
        if (!templatesPerFile.hasTemplateWithUnqualifiedName(name)) {
          reportUnknownSymbolError(
              symbol.nameLocation(),
              name,
              node.getPath(),
              /* validSymbols= */ templatesPerFile.getUnqualifiedTemplateNames());
          continue;
        }
        symbolsToTemplatesMap.put(symbol.aliasOrName(), templatesPerFile.getFullTemplateName(name));
      }
      node.setIsResolved(); // Node has been validated
    }

    @Override
    boolean importExists(ImportType type, String path) {
      // We can ignore the type param because this visitor only visits template imports.
      return templateNameRegistry.hasFile(path);
    }

    @Override
    ImmutableSet<String> getValidImportPathsForType(ImportType type) {
      // Get the names of all Soy files registered in the file set (including its deps). We can
      // ignore the type param because this visitor only visits template imports.
      return templateNameRegistry.allFiles();
    }

    @Override
    void updateImportsContext() {
      file.getImportsContext()
          .setTemplateRegistry(new ImportsTemplateRegistry(file, symbolsToTemplatesMap.build()));
    }
  }
}

@@ -1493,16 +1493,70 @@
 rtedVar;
+%0Aimport java.util.LinkedHashMap;%0Aimport java.util.Map;
 %0A%0A/**%0A *
@@ -3020,36 +3020,19 @@
   final 
-ImmutableMap.Builder
+Map
 %3CString,
@@ -3073,41 +3073,26 @@
 ap =
-%0A        new ImmutableMap.Builder
+ new LinkedHashMap
 %3C%3E()
@@ -4283,24 +4283,177 @@
 ;%0A        %7D%0A
+        // Needs to be able to handle duplicates, since the formatter fixes them, but it's not a%0A        // compiler error (if they have the same path).%0A
         symb
@@ -5253,16 +5253,31 @@
 egistry(
+%0A              
 new Impo
@@ -5302,16 +5302,36 @@
 y(file, 
+ImmutableMap.copyOf(
 symbolsT
@@ -5347,15 +5347,8 @@
 sMap
-.build(
 )));

Add handling for [] (which stopped duplicate imports from being a compiler error) for template imports by not crashing on duplicate keys in the import registry.