/*
 * 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.incrementaldomsrc;

import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.html.AbstractHtmlSoyNodeVisitor;
import com.google.template.soy.internal.base.UnescapeUtils;
import com.google.template.soy.soytree.CallParamContentNode;
import com.google.template.soy.soytree.HtmlContext;
import com.google.template.soy.soytree.LetContentNode;
import com.google.template.soy.soytree.MsgFallbackGroupNode;
import com.google.template.soy.soytree.MsgPlaceholderNode;
import com.google.template.soy.soytree.RawTextNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SoyNode.RenderUnitNode;
import com.google.template.soy.soytree.SoytreeUtils;
import com.google.template.soy.soytree.TemplateNode;

/**
 * HTML-unescapes all {@link RawTextNode}s in {@link ContentKind#HTML}
 * or {@link ContentKind#ATTRIBUTES} contexts. This is used for Incremental DOM compilation,
 * which treats raw content in these contexts as text rather than HTML source.
 */
final class UnescapingVisitor extends AbstractHtmlSoyNodeVisitor<Void> {

  @Override protected void visitSoyNode(SoyNode node) {
    if (node instanceof ParentSoyNode<?>) {
      visitChildrenAllowingConcurrentModification((ParentSoyNode<?>) node);
    }
  }

  @Override protected void visitRawTextNode(RawTextNode node) {
    if (node.getHtmlContext() != HtmlContext.HTML_PCDATA
        && node.getHtmlContext() != HtmlContext.HTML_NORMAL_ATTR_VALUE) {
      return;
    }
    // Don't unescape the raw translated text nodes within an {msg} tag.  Do escape text nodes which
    // are nested in placeholders inside {msg} tags (since they aren't directly translated).  Unless
    // they're further in {msg} tags that are nested in placeholders.  Don't worry; we've got tests!
    MsgFallbackGroupNode containingMsg = node.getNearestAncestor(MsgFallbackGroupNode.class);
    if (containingMsg != null) {
      MsgPlaceholderNode containingPlaceholder = node.getNearestAncestor(MsgPlaceholderNode.class);
      // Unless we're _directly_ in a placeholder.
      if (containingPlaceholder == null
          || !SoytreeUtils.isDescendantOf(containingPlaceholder, containingMsg)) {
        return;
      }
    }
    node.getParent().replaceChild(node, new RawTextNode(node.getId(),
        UnescapeUtils.unescapeHtml(node.getRawText()),
        node.getSourceLocation(), node.getHtmlContext()));
  }

  @Override protected void visitTemplateNode(TemplateNode node) {
    visitRenderUnitNode(node);
  }

  @Override protected void visitCallParamContentNode(CallParamContentNode node) {
    visitRenderUnitNode(node);
  }

  @Override protected void visitLetContentNode(LetContentNode node) {
    visitRenderUnitNode(node);
  }

  private void visitRenderUnitNode(RenderUnitNode node) {
    if (node.getContentKind() == ContentKind.HTML
        || node.getContentKind() == ContentKind.ATTRIBUTES) {
      visitSoyNode(node);
    }
  }
}
