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

import static com.google.common.base.Preconditions.checkState;

import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprparse.ExpressionParser;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.soytree.ForeachIfemptyNode;
import com.google.template.soy.soytree.ForeachNode;
import com.google.template.soy.soytree.ForeachNonemptyNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A helper for building {@link ForeachNode}s and its two immediate children: 
 * {@link ForeachNonemptyNode} and {@link ForeachIfemptyNode}.
 */
final class ForeachBuilder {

  static final SoyErrorKind INVALID_COMMAND_TEXT =
      SoyErrorKind.of("Invalid ''foreach'' command text \"{0}\".");

  /** Regex pattern for the command text. */
  // 2 capturing groups: local var name, expression
  private static final Pattern FOR_EACH_COMMAND_TEXT_PATTERN =
      Pattern.compile("( [$] \\w+ ) \\s+ in \\s+ (\\S .*)", Pattern.COMMENTS | Pattern.DOTALL);

  static ForeachBuilder create(IdGenerator nodeIdGen, SoyParsingContext context) {
    return new ForeachBuilder(nodeIdGen, context);
  }

  private final IdGenerator nodeIdGen;
  private final SoyParsingContext context;
  private String cmdText;
  private List<StandaloneNode> templateBlock;

  private SourceLocation ifEmptyLocation;
  private List<StandaloneNode> ifEmptyBlock;
  private SourceLocation commandLocation;

  private ForeachBuilder(IdGenerator nodeIdGen, SoyParsingContext context) {
    this.nodeIdGen = nodeIdGen;
    this.context = context;
  }

  ForeachBuilder setCommandLocation(SourceLocation location) {
    this.commandLocation = location;
    return this;
  }

  ForeachBuilder setCommandText(String cmdText) {
    this.cmdText = cmdText;
    return this;
  }
  
  ForeachBuilder setLoopBody(List<StandaloneNode> templateBlock) {
    this.templateBlock = templateBlock;
    return this;
  }

  ForeachBuilder setIfEmptyBody(SourceLocation ifEmptyLocation,
      List<StandaloneNode> ifEmptyBlock) {
    this.ifEmptyLocation = ifEmptyLocation;
    this.ifEmptyBlock = ifEmptyBlock;
    return this;
  }

  ForeachNode build() {
    checkState(cmdText != null, "You must call .setCommandText()");
    checkState(commandLocation != null, "You must call .setCommandLocation()");
    checkState(templateBlock != null, "You must call .setLoopBody()");

    String varName = "__error__";
    ExprRootNode expr = null;
    Matcher matcher = FOR_EACH_COMMAND_TEXT_PATTERN.matcher(cmdText);
    if (!matcher.matches()) {
      context.report(commandLocation, INVALID_COMMAND_TEXT, cmdText);
    } else {
      varName = new ExpressionParser(matcher.group(1), commandLocation, context)
          .parseVariable()
          .getName();
      expr = new ExprRootNode(
          new ExpressionParser(matcher.group(2), commandLocation, context)
              .parseExpression());
    }
    
    ForeachNode foreach = new ForeachNode(nodeIdGen.genId(), expr, cmdText, commandLocation);
    ForeachNonemptyNode nonEmpty
        = new ForeachNonemptyNode(nodeIdGen.genId(), varName, commandLocation);
    nonEmpty.addChildren(templateBlock);
    foreach.addChild(nonEmpty);
    if (ifEmptyBlock != null) {
      ForeachIfemptyNode ifEmpty = new ForeachIfemptyNode(nodeIdGen.genId(), ifEmptyLocation);
      ifEmpty.addChildren(ifEmptyBlock);
      foreach.addChild(ifEmpty);
    }
    return foreach;
  }
}
