/*
 * Copyright 2008 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 com.google.template.soy.base.SourceLocation;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.basetree.MixinParentNode;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.error.ErrorReporter.Checkpoint;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.soytree.SoyNode.RenderUnitNode;

import java.util.List;

import javax.annotation.Nullable;

/**
 * Node representing a 'param' with content.
 *
 * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
 *
 */
public final class CallParamContentNode extends CallParamNode implements RenderUnitNode {

  private static final SoyErrorKind PARAM_HAS_VALUE_BUT_IS_NOT_SELF_CLOSING =
      SoyErrorKind.of(
          "A ''param'' tag should contain a value if and only if it is also self-ending "
              + "(with a trailing ''/'') (invalid tag is '{'param {0}'}').");

  /** The mixin object that implements the ParentNode functionality. */
  private final MixinParentNode<StandaloneNode> parentMixin;

  /** The param key. */
  private final String key;

  /** The param's content kind, or null if no 'kind' attribute was present. */
  @Nullable private final ContentKind contentKind;


  /**
   * @param id The id for this node.
   * @param sourceLocation The node's source location.
   * @param commandText The command text.
   */
  private CallParamContentNode(
      int id,
      SourceLocation sourceLocation,
      String key,
      ContentKind contentKind,
      String commandText) {
    super(id, sourceLocation, commandText);
    parentMixin = new MixinParentNode<>(this);

    this.key = key;
    this.contentKind = contentKind;
  }


  /**
   * Copy constructor.
   * @param orig The node to copy.
   */
  private CallParamContentNode(CallParamContentNode orig, CopyState copyState) {
    super(orig, copyState);
    this.parentMixin = new MixinParentNode<>(orig.parentMixin, this, copyState);
    this.key = orig.key;
    this.contentKind = orig.contentKind;
  }


  @Override public Kind getKind() {
    return Kind.CALL_PARAM_CONTENT_NODE;
  }


  @Override public String getKey() {
    return key;
  }


  @Override @Nullable public ContentKind getContentKind() {
    return contentKind;
  }


  // -----------------------------------------------------------------------------------------------
  // ParentSoyNode stuff.
  // Note: Most concrete nodes simply inherit this functionality from AbstractParentCommandNode or
  // AbstractParentSoyNode. But this class need to include its own MixinParentNode field because
  // it needs to subclass CallParamNode (and Java doesn't allow multiple inheritance).


  @Override public String toSourceString() {
    StringBuilder sb = new StringBuilder();
    sb.append(getTagString());
    appendSourceStringForChildren(sb);
    sb.append("{/").append(getCommandName()).append('}');
    return sb.toString();
  }

  @Override public int numChildren() {
    return parentMixin.numChildren();
  }

  @Override public StandaloneNode getChild(int index) {
    return parentMixin.getChild(index);
  }

  @Override public int getChildIndex(StandaloneNode child) {
    return parentMixin.getChildIndex(child);
  }

  @Override public List<StandaloneNode> getChildren() {
    return parentMixin.getChildren();
  }

  @Override public void addChild(StandaloneNode child) {
    parentMixin.addChild(child);
  }

  @Override public void addChild(int index, StandaloneNode child) {
    parentMixin.addChild(index, child);
  }

  @Override public void removeChild(int index) {
    parentMixin.removeChild(index);
  }

  @Override public void removeChild(StandaloneNode child) {
    parentMixin.removeChild(child);
  }

  @Override public void replaceChild(int index, StandaloneNode newChild) {
    parentMixin.replaceChild(index, newChild);
  }

  @Override public void replaceChild(StandaloneNode currChild, StandaloneNode newChild) {
    parentMixin.replaceChild(currChild, newChild);
  }

  @Override public void clearChildren() {
    parentMixin.clearChildren();
  }

  @Override public void addChildren(List<? extends StandaloneNode> children) {
    parentMixin.addChildren(children);
  }

  @Override public void addChildren(int index, List<? extends StandaloneNode> children) {
    parentMixin.addChildren(index, children);
  }

  @Override public void appendSourceStringForChildren(StringBuilder sb) {
    parentMixin.appendSourceStringForChildren(sb);
  }

  @Override public CallParamContentNode copy(CopyState copyState) {
    return new CallParamContentNode(this, copyState);
  }

  public static final class Builder extends CallParamNode.Builder {

    private static CallParamContentNode error() {
      return new Builder(-1, "error", SourceLocation.UNKNOWN)
          .build(SoyParsingContext.exploding()); // guaranteed to build
    }

    public Builder(int id, String commandText, SourceLocation sourceLocation) {
      super(id, commandText, sourceLocation);
    }

    public CallParamContentNode build(SoyParsingContext context) {
      Checkpoint checkpoint = context.errorReporter().checkpoint();
      CommandTextParseResult parseResult = parseCommandTextHelper(context);
      if (parseResult.valueExprUnion != null) {
        context.report(sourceLocation, PARAM_HAS_VALUE_BUT_IS_NOT_SELF_CLOSING, commandText);
      }

      if (context.errorReporter().errorsSince(checkpoint)) {
        return error();
      }

      return new CallParamContentNode(
          id, sourceLocation, parseResult.key, parseResult.contentKind, commandText);
    }
  }

}
