/*
 * Copyright 2015 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.jbcsrc;


import com.google.auto.value.AutoValue;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.jbcsrc.restricted.BytecodeUtils;
import com.google.template.soy.jbcsrc.restricted.MethodRef;
import com.google.template.soy.jbcsrc.restricted.TypeInfo;
import com.google.template.soy.jbcsrc.shared.CompiledTemplate;
import com.google.template.soy.jbcsrc.shared.Names;
import com.google.template.soy.jbcsrc.shared.RenderContext;
import com.google.template.soy.soytree.TemplateMetadata;
import com.google.template.soy.types.TemplateType;
import java.util.Optional;
import java.util.stream.Stream;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;

/**
 * Information about a compiled template.
 *
 * <p>This should contain basic information about a single template that will be useful for
 * generating that template as well as calls to the template.
 */
@AutoValue
abstract class CompiledTemplateMetadata {
  /**
   * The {@link Method} signature of the {@link
   * CompiledTemplate#render(SoyRecord,SoyRecord,AdvisingAppendable, RenderContext)} method.
   */
  static final Method RENDER_METHOD =
      new Method(
          "render",
          Type.getMethodDescriptor(
              BytecodeUtils.RENDER_RESULT_TYPE,
              BytecodeUtils.SOY_RECORD_TYPE,
              BytecodeUtils.SOY_RECORD_TYPE,
              BytecodeUtils.LOGGING_ADVISING_APPENDABLE_TYPE,
              BytecodeUtils.RENDER_CONTEXT_TYPE));

  /** Generates a method signature for a positional style call to the given template. */
  private static Method createPositionalRenderMethod(TemplateType templateType) {
    return new Method(
        "render",
        Type.getMethodDescriptor(
            BytecodeUtils.RENDER_RESULT_TYPE,
            Stream.concat(
                    templateType.getActualParameters().stream()
                        .map(i -> BytecodeUtils.SOY_VALUE_PROVIDER_TYPE),
                    Stream.of(
                        BytecodeUtils.SOY_RECORD_TYPE,
                        BytecodeUtils.LOGGING_ADVISING_APPENDABLE_TYPE,
                        BytecodeUtils.RENDER_CONTEXT_TYPE))
                .toArray(Type[]::new)));
  }

  /** The {@link Method} signature of the {@code static CompiledTemplate template()} method. */
  private static final Method TEMPLATE_METHOD =
      new Method("template", Type.getMethodDescriptor(BytecodeUtils.COMPILED_TEMPLATE_TYPE));

  static CompiledTemplateMetadata create(TemplateMetadata metadata) {
    String className = Names.javaClassNameFromSoyTemplateName(metadata.getTemplateName());
    TypeInfo type = TypeInfo.createClass(className);
    TemplateType templateType = metadata.getTemplateType();
    // Decide whether or not to use a positional style call signature.
    // Positional parameters are not possible to do if there are indirect calls since those may
    // require parameters not declared in our signature.
    // If there are no parameters, then there is no value in exploding into multiple functions
    // If the template is a Soy element, then we also need the `opt_data` object.
    boolean hasPositionalSignature =
        templateType.getDataAllCallSituations().isEmpty()
            // only basic/element templates are supported for now.
            // deltemplates require the object style to support the relatively weak type checking we
            // perform on them.
            && templateType.getTemplateKind() != TemplateType.TemplateKind.DELTEMPLATE;

    return new AutoValue_CompiledTemplateMetadata(
        MethodRef.createStaticMethod(type, RENDER_METHOD).asNonNullable(),
        Optional.ofNullable(
            hasPositionalSignature
                ? MethodRef.createStaticMethod(type, createPositionalRenderMethod(templateType))
                    .asNonNullable()
                : null),
        MethodRef.createStaticMethod(type, TEMPLATE_METHOD).asCheap().asNonNullable(),
        templateType,
        type);
  }

  /**
   * The {@code static RenderResult render(SoyRecord,SoyRecord, LoggingAdvisingAppendable,
   * RenderContext)} method.
   */
  abstract MethodRef renderMethod();

  /**
   * The {@code static RenderResult render(SoyValueProvider,...,SoyValueProvider,SoyRecord,
   * LoggingAdvisingAppendable, RenderContext)} overload method.
   */
  abstract Optional<MethodRef> positionalRenderMethod();

  /** The {@code static CompiledTemplate template()} method. */
  abstract MethodRef templateMethod();

  boolean hasPositionalSignature() {
    return positionalRenderMethod().isPresent();
  }

  abstract TemplateType templateType();

  /** The name of the compiled template class. */
  abstract TypeInfo typeInfo();
}

@@ -3823,16 +3823,362 @@
 Empty()%0A
+            // Skip positional signatures when there are no parameters.  This is not necessary for%0A            // correctness however in this case there is very little benefit in the positional%0A            // overload and so we can generate one fewer method by just not generating it.%0A            && !templateType.getActualParameters().isEmpty()%0A
         

Avoid creating positional parameter style templates when the template has no formal parameters.