/*
 * Copyright 2009 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.soytree;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.template.soy.SoyFileSetParserBuilder;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.base.internal.IncrementingIdGenerator;
import com.google.template.soy.base.internal.SoyFileKind;
import com.google.template.soy.basetree.SyntaxVersion;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.ExplodingErrorReporter;
import com.google.template.soy.exprtree.AbstractExprNodeVisitor;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprNode.ParentExprNode;
import com.google.template.soy.exprtree.VarDefn;
import com.google.template.soy.exprtree.VarRefNode;
import com.google.template.soy.soyparse.SoyFileParser;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
import com.google.template.soy.soytree.defn.LocalVar;
import com.google.template.soy.types.SoyTypeRegistry;

import junit.framework.TestCase;

import java.io.StringReader;
import java.util.List;

/**
 * Unit tests for SoytreeUtils.
 *
 */
public final class SoytreeUtilsTest extends TestCase {


  // -----------------------------------------------------------------------------------------------
  // Tests for executing an ExprNode visitor on all expressions in a Soy tree.


  public void testVisitAllExprs() {

    String testFileContent =
        "{namespace boo autoescape=\"deprecated-noncontextual\"}\n" +
        "\n" +
        "/** @param items */\n" +
        "{template .foo}\n" +
        "  {length($items) + 5}\n" +  // 5 nodes
        "  {foreach $item in $items}\n" +  // 2 nodes
        "    {$item.goo}\n" +  // 3 nodes
        "  {/foreach}\n" +
        "{/template}\n";

    ErrorReporter boom = ExplodingErrorReporter.get();
    SoyFileSetNode soyTree =
        SoyFileSetParserBuilder.forFileContents(testFileContent)
            .errorReporter(boom)
            .parse()
            .fileSet();

    CountingVisitor countingVisitor = new CountingVisitor();
    SoytreeUtils.execOnAllV2Exprs(soyTree, countingVisitor);
    CountingVisitor.Counts counts = countingVisitor.getCounts();
    assertEquals(3, counts.numExecs);
    assertEquals(10, counts.numVisitedNodes);
  }


  /**
   * Visitor that counts the number of times {@code exec()} is called and the total number of nodes
   * visited over all of those calls.
   *
   * <p> Helper class for {@code testVisitAllExprs()}.
   */
  private static class CountingVisitor extends AbstractExprNodeVisitor<Void> {

    public static class Counts {
      public int numExecs;
      public int numVisitedNodes;
    }

    private final Counts counts = new Counts();

    public Counts getCounts() {
      return counts;
    }

    @Override public Void exec(ExprNode node) {
      counts.numExecs++;
      return super.exec(node);
    }

    @Override protected void visitExprNode(ExprNode node) {
      counts.numVisitedNodes++;
      if (node instanceof ParentExprNode) {
        visitChildren((ParentExprNode) node);
      }
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Tests for cloning. Adapted from Mike Samuel's CloneVisitorTest.


  private static final String SOY_SOURCE_FOR_TESTING_CLONING = Joiner.on('\n').join(
      "{namespace ns autoescape=\"deprecated-noncontextual\"}",
      "/** example for cloning. */",
      "{template .ex1 private=\"true\"}",
      "  {@param a : ?}",
      "  {@param b : ?}",
      "  {@param c : ?}",
      "  {@param v : ?}",
      "  {@param x : ?}",
      "  {@param start : ?}",
      "  {@param end : ?}",
      "  {@param cond0 : ?}",
      "  {@param cond1 : ?}",
      "  {@param items : ?}",
      "  {@param world : ?}",
      "  {@param foo : ?}",
      "  Hello, World!",
      "  {lb}{call foo data=\"all\"}{param x: $x /}{/call}{rb}",
      "  {$x |escapeHtml}",
      "  {if $cond0}",
      "    {$a}",
      "  {elseif $cond1}",
      "    {print $b}",
      "  {else}",
      "    {$c}",
      "  {/if}",
      "  {switch $v}",
      "    {case 0}",
      "      Zero",
      "    {default}",
      "      Some",
      "  {/switch}",
      "  {literal}",
      "     {interpreted literally/}",
      "  {/literal}",
      "  <style>{css $foo} {lb}color: red{rb}</style>",
      "  {msg desc=\"test\"}<h1 class=\"howdy\">Hello, {$world}!</h1>{/msg}",
      "  <ol>",
      "    {foreach $item in $items}",
      "      <li>{$item}</li>",
      "    {ifempty}",
      "      <li><i>Nothing to see here!",
      "    {/foreach}",
      "    {for $i in range($start, $end)}",
      "      <li value={$i}>foo</li>",
      "    {/for}",
      "  </ol>",
      "  {let $local : 'foo' /}",
      "  {$local}",
      "{/template}");


  public final void testClone() throws Exception {
    SoyFileSetNode soyTree =
        SoyFileSetParserBuilder.forFileContents(SOY_SOURCE_FOR_TESTING_CLONING)
            .declaredSyntaxVersion(SyntaxVersion.V2_4)
            .parse()
            .fileSet();

    SoyFileSetNode clone = SoytreeUtils.cloneNode(soyTree);
    assertEquals(1, clone.numChildren());

    assertEquals(clone.getChild(0).toSourceString(), soyTree.getChild(0).toSourceString());
    // All the localvarnodes, there is one of each type
    ForNode forNode =
        Iterables.getOnlyElement(SoytreeUtils.getAllNodesOfType(clone, ForNode.class));
    ForeachNonemptyNode foreachNonemptyNode =
        Iterables.getOnlyElement(SoytreeUtils.getAllNodesOfType(clone, ForeachNonemptyNode.class));
    LetValueNode letValueNode =
        Iterables.getOnlyElement(SoytreeUtils.getAllNodesOfType(clone, LetValueNode.class));
    for (VarRefNode varRef : SoytreeUtils.getAllNodesOfType(clone, VarRefNode.class)) {
      VarDefn defn = varRef.getDefnDecl();
      LocalVar local;
      switch (varRef.getName()) {
        case "local":
          local = (LocalVar) defn;
          assertSame(letValueNode, local.declaringNode());
          assertSame(letValueNode.getVar(), local);
          break;
        case "item":
          local = (LocalVar) defn;
          assertSame(foreachNonemptyNode, local.declaringNode());
          assertSame(foreachNonemptyNode.getVar(), defn);
          break;
        case "i":
          local = (LocalVar) defn;
          assertSame(forNode, local.declaringNode());
          assertSame(forNode.getVar(), defn);
          break;
      }
    }
  }


  public final void testCloneWithNewIds() throws Exception {

    IdGenerator nodeIdGen = new IncrementingIdGenerator();
    SoyFileSetNode soyTree = new SoyFileSetNode(nodeIdGen.genId(), nodeIdGen);
    SoyFileNode soyFile = new SoyFileParser(
        new SoyTypeRegistry(),
        nodeIdGen,
        new StringReader(SOY_SOURCE_FOR_TESTING_CLONING),
        SoyFileKind.SRC,
        "test.soy",
        ExplodingErrorReporter.get())
        .parseSoyFile();
    soyTree.addChild(soyFile);

    SoyFileSetNode clone = SoytreeUtils.cloneWithNewIds(soyTree, nodeIdGen);
    assertEquals(1, clone.numChildren());

    assertFalse(clone.getId() == soyTree.getId());
    assertEquals(clone.getChild(0).toSourceString(), soyFile.toSourceString());
  }


  public final void testCloneListWithNewIds() throws Exception {

    IdGenerator nodeIdGen = new IncrementingIdGenerator();
    SoyFileSetNode soyTree = new SoyFileSetNode(nodeIdGen.genId(), nodeIdGen);
    SoyFileNode soyFile = new SoyFileParser(
        new SoyTypeRegistry(),
        nodeIdGen,
         new StringReader(SOY_SOURCE_FOR_TESTING_CLONING),
        SoyFileKind.SRC,
        "test.soy",
        ExplodingErrorReporter.get())
        .parseSoyFile();
    soyTree.addChild(soyFile);

    TemplateNode template = soyFile.getChild(0);
    int numChildren = template.numChildren();

    List<StandaloneNode> clones = SoytreeUtils.cloneListWithNewIds(
        template.getChildren(), nodeIdGen);
    assertThat(clones).hasSize(numChildren);

    for (int i = 0; i < numChildren; i++) {
      StandaloneNode clone = clones.get(i);
      StandaloneNode child = template.getChild(i);
      assertFalse(clone.getId() == child.getId());
      assertEquals(child.toSourceString(), clone.toSourceString());
    }
  }


  /**
   * Private helper for testCloneWithNewIds().
   */
  private static String ignoreNodeIds(String treeString) {
    // Nodes in a treeString look like "[<NodeClass>_<Id>]".
    // The function cloneWithNewIds should generate new IDs so normalize the IDs to suppress
    // expected differences.
    return treeString.replaceAll("_(\\d+)\\]", "_#]");
  }


  public final void testMsgHtmlTagNode() throws Exception {

    IdGenerator nodeIdGen = new IncrementingIdGenerator();
    SoyFileSetNode soyTree = new SoyFileSetNode(nodeIdGen.genId(), nodeIdGen);
    ErrorReporter boom = ExplodingErrorReporter.get();
    SoyFileNode soyFile = new SoyFileParser(
        new SoyTypeRegistry(),
        nodeIdGen,
        new StringReader(SOY_SOURCE_FOR_TESTING_CLONING),
        SoyFileKind.SRC,
        "test.soy",
        boom)
        .parseSoyFile();
    soyTree.addChild(soyFile);

    List<MsgHtmlTagNode> msgHtmlTagNodes =
        SoytreeUtils.getAllNodesOfType(soyFile, MsgHtmlTagNode.class);

    for (MsgHtmlTagNode origMsgHtmlTagNode : msgHtmlTagNodes) {
      MsgHtmlTagNode clonedMsgHtmlTagNode = SoytreeUtils.cloneNode(origMsgHtmlTagNode);

      assertEquals(clonedMsgHtmlTagNode.numChildren(), origMsgHtmlTagNode.numChildren());
      assertEquals(clonedMsgHtmlTagNode.getId(), origMsgHtmlTagNode.getId());
      assertEquals(clonedMsgHtmlTagNode.getFullTagText(), origMsgHtmlTagNode.getFullTagText());
      assertEquals(clonedMsgHtmlTagNode.getLcTagName(), origMsgHtmlTagNode.getLcTagName());
      assertEquals(
          clonedMsgHtmlTagNode.getSyntaxVersionUpperBound(),
          origMsgHtmlTagNode.getSyntaxVersionUpperBound());
    }
  }

}
