/*
 * Copyright 2011 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.passes;

import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.MsgPluralNode;
import com.google.template.soy.soytree.PrintNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;

/**
 * Visitor for finding {@code print} nodes that are actually {@code remainder} nodes,
 * and replacing them with the appropriate expression.
 *
 * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
 *
 * <p> {@link #exec} should be called on a full parse tree. There is no return value.
 *
 */
public final class RewriteRemaindersVisitor extends AbstractSoyNodeVisitor<Void> {

  private static final SoyErrorKind REMAINDER_ARITY_MISMATCH =
      SoyErrorKind.of("''remainder'' called with {0} arguments, expected 1.");
  private static final SoyErrorKind REMAINDER_OUTSIDE_PLURAL =
      SoyErrorKind.of("Special function ''remainder'' is for use in plural messages only.");
  private static final SoyErrorKind REMAINDER_PLURAL_EXPR_MISMATCH =
      SoyErrorKind.of("Argument to ''remainder'' must be the same as the ''plural'' variable");
  private static final SoyErrorKind REMAINDER_UNNECESSARY_AT_OFFSET_0 =
      SoyErrorKind.of("''remainder'' is unnecessary since offset=0.");
  private static final SoyErrorKind REMAINDER_WITH_PHNAME =
      SoyErrorKind.of("Special function ''remainder'' cannot be used with ''phname''.");

  /** The MsgPluralNode most recently visited. */
  private MsgPluralNode currPluralNode;

  private final ErrorReporter errorReporter;

  public RewriteRemaindersVisitor(ErrorReporter errorReporter) {
    this.errorReporter = errorReporter;
  }

  // -----------------------------------------------------------------------------------------------
  // Implementations for specific nodes.


  @Override protected void visitPrintNode(PrintNode node) {
    // We cannot easily access the original context.  However, because everything has already been
    // parsed, that should be fine.  I don't think this can fail at all, but whatever.
    SoyParsingContext context = SoyParsingContext.empty(errorReporter, "fake.namespace");

    ExprRootNode exprRootNode = node.getExprUnion().getExpr();
    if (exprRootNode == null) {
      return;
    }

    // Check for the function node with the function "remainder()".
    if (exprRootNode.getRoot() instanceof FunctionNode) {
      FunctionNode functionNode = (FunctionNode) exprRootNode.getRoot();
      if (functionNode.getFunctionName().equals("remainder")) {

        // 'remainder' outside 'plural'. Bad!
        if (currPluralNode == null) {
          errorReporter.report(functionNode.getSourceLocation(), REMAINDER_OUTSIDE_PLURAL);
          return; // to prevent NPE later in the method
        }

        // 'remainder' with no parameters or more than one parameter. Bad!
        if (functionNode.numChildren() != 1) {
          errorReporter.report(
              functionNode.getSourceLocation(),
              REMAINDER_ARITY_MISMATCH,
              functionNode.numChildren());
        }

        // 'remainder' with a different expression than the enclosing 'plural'. Bad!
        if (!functionNode.getChild(0).toSourceString().equals(
                  currPluralNode.getExpr().toSourceString())) {
          errorReporter.report(functionNode.getSourceLocation(), REMAINDER_PLURAL_EXPR_MISMATCH);
        }

        // 'remainder' with a 0 offset. Bad!
        if (currPluralNode.getOffset() == 0) {
          errorReporter.report(functionNode.getSourceLocation(), REMAINDER_UNNECESSARY_AT_OFFSET_0);
        }

        // 'remainder' with 'phname' attribute. Bad!
        if (node.getUserSuppliedPhName() != null) {
          errorReporter.report(functionNode.getSourceLocation(), REMAINDER_WITH_PHNAME);
        }

        // Now rewrite the PrintNode (reusing the old node id).
        String newExprText =
            "(" + currPluralNode.getExpr().toSourceString() + ") - " + currPluralNode.getOffset();
        PrintNode newPrintNode
            = new PrintNode.Builder(node.getId(), node.isImplicit(), SourceLocation.UNKNOWN)
                .exprText(newExprText)
                .build(context);
        newPrintNode.addChildren(node.getChildren());
        node.getParent().replaceChild(node, newPrintNode);
      }
    }
  }


  @Override protected void visitMsgPluralNode(MsgPluralNode node) {
    currPluralNode = node;
    visitChildren(node);
    currPluralNode = null;
  }


  // -----------------------------------------------------------------------------------------------
  // Fallback implementation.


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

}
