/*
 * Copyright 2016 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.jssrc.dsl;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import javax.annotation.Nullable;

/** Represents a variable declaration. */
@AutoValue
@Immutable
public abstract class VariableDeclaration extends Statement {

  public static Builder builder(String name) {
    return new AutoValue_VariableDeclaration.Builder()
        .setVarName(name)
        .setGoogRequires(ImmutableSet.of());
  }

  abstract String varName();

  @Nullable
  abstract Expression rhs();

  @Nullable
  abstract JsDoc jsDoc();

  abstract ImmutableSet<GoogRequire> googRequires();

  /** Returns an {@link Expression} representing a reference to this declared variable. */
  public Expression ref() {
    return VariableReference.of(this);
  }

  /**
   * {@link CodeChunk#getCode} serializes both the chunk's initial statements and its output
   * expression. When a declaration is the only chunk being serialized, this leads to a redundant
   * trailing expression: <code>
   *   var $$tmp = blah;
   *   $$tmp
   * </code> Override the superclass implementation to omit the trailing expression.
   */
  @Override
  String getCode(int startingIndent) {
    FormattingContext ctx = new FormattingContext(startingIndent);
    ctx.appendInitialStatements(this);
    return ctx.toString();
  }

  @Override
  void doFormatInitialStatements(FormattingContext ctx) {
    if (rhs() != null) {
      ctx.appendInitialStatements(rhs());
    }
    if (jsDoc() != null) {
      ctx.append(jsDoc()).endLine();
    }
    ctx.append("var ").append(varName());
    if (rhs() != null) {
      ctx.append(" = ").appendOutputExpression(rhs());
    }
    ctx.append(";").endLine();
  }

  @Override
  public void collectRequires(RequiresCollector collector) {
    for (GoogRequire require : googRequires()) {
      collector.add(require);
    }
    if (rhs() != null) {
      rhs().collectRequires(collector);
    }
    if (jsDoc() != null) {
      jsDoc().collectRequires(collector);
    }
  }

  /** A builder for a {@link VariableDeclaration}. */
  @AutoValue.Builder
  public abstract static class Builder {

    abstract Builder setVarName(String name);

    public abstract Builder setJsDoc(JsDoc jsDoc);

    public abstract Builder setRhs(Expression value);

    public Builder setGoogRequires(Iterable<GoogRequire> requires) {
      return setGoogRequires(ImmutableSet.copyOf(requires));
    }

    abstract Builder setGoogRequires(ImmutableSet<GoogRequire> requires);

    public abstract VariableDeclaration build();
  }
}

@@ -695,35 +695,147 @@
 gle.
-common.collect.ImmutableSet
+auto.value.extension.memoized.Memoized;%0Aimport com.google.common.collect.ImmutableSet;%0Aimport com.google.errorprone.annotations.ForOverride
 ;%0Aim
@@ -2581,20 +2581,19 @@
 quire : 
-goog
+all
 Requires
@@ -2626,32 +2626,610 @@
 require);%0A    %7D%0A
+  %7D%0A%0A  // A cache of all the transitive requires.  Necessary because every time we traverse a variable%0A  // reference we collect requires from the declaration.  This means collectRequires will be called%0A  // once per reference instead of once per declaration which can lead to exponential behavior.%0A  // Use an array so our caller doesn't have to deal with allocating epic amounts of iterators.%0A  @ForOverride%0A  @Memoized%0A  GoogRequire%5B%5D allRequires() %7B%0A    ImmutableSet.Builder%3CGoogRequire%3E requiresBuilder =%0A        ImmutableSet.%3CGoogRequire%3Ebuilder().addAll(googRequires());%0A
     if (rhs() !=
@@ -3261,33 +3261,44 @@
 ectRequires(
-collector
+requiresBuilder::add
 );%0A    %7D%0A   
@@ -3355,25 +3355,100 @@
 res(
-collector);%0A    %7D
+requiresBuilder::add);%0A    %7D%0A    return requiresBuilder.build().toArray(new GoogRequire%5B0%5D);
 %0A  %7D

Fix a expontential performance issue in how we collect goog requires from variable references