/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * 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 jetbrains.mps.generator.impl;

import jetbrains.mps.generator.GenerationCanceledException;
import jetbrains.mps.generator.GenerationTrace;
import jetbrains.mps.generator.GenerationTracerUtil;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment;
import jetbrains.mps.generator.template.ITemplateProcessor;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Container for Template Fragments, collects them and applies through supplied TemplateProcessor.
 * <p>For weave rule/macro there's {@link jetbrains.mps.generator.impl.WeaveTemplateContainer} counterpart.
 * @author Artem Tikhomirov
 */
public class TemplateContainer extends RuleConsequenceProcessor {
  private final SNode myTemplateNode;
  private List<Pair<SNode, String>> myNodeAndMappingNamePairs;

  public TemplateContainer(@NotNull SNode templateContainer) {
    myTemplateNode = templateContainer;
  }

  public TemplateContainer(@NotNull Pair<SNode, String> fragment) {
    myTemplateNode = null;
    myNodeAndMappingNamePairs = Collections.singletonList(fragment);
  }

  /*
   * Initialize container once for a template, then apply multiple times.
   */
  private void initialize() throws TemplateProcessingFailureException {
    if (myNodeAndMappingNamePairs != null) {
      return;
    }
    List<SNode> fragments = checkAdjacentFragments();
    List<Pair<SNode, String>> result = new ArrayList<Pair<SNode, String>>(fragments.size());
    for (SNode fragment : fragments) {
      result.add(new Pair<SNode, String>(SNodeOperations.getParent(fragment), GeneratorUtilEx.getMappingName_TemplateFragment(fragment, null)));
    }
    myNodeAndMappingNamePairs = result;
  }

  @NotNull
  @Override
  public List<SNode> processRuleConsequence(@NotNull TemplateContext ctx)
      throws GenerationFailureException, DismissTopMappingRuleException, GenerationCanceledException {
    initialize();
    ArrayList<SNode> outputNodes = new ArrayList<SNode>();
    final TemplateExecutionEnvironment environment = ctx.getEnvironment();
    final GenerationTrace tracer = environment.getTrace();
    ITemplateProcessor templateProcessor = environment.getTemplateProcessor();
    for (Pair<SNode, String> nodeAndMappingNamePair : myNodeAndMappingNamePairs) {
      SNode templateNode = nodeAndMappingNamePair.o1;
      String innerMappingName = nodeAndMappingNamePair.o2;
      List<SNode> _outputNodes = templateProcessor.apply(templateNode, ctx.subContext(innerMappingName));
      SNode input = ctx.getInput();
      tracer.trace(input == null ? null : input.getNodeId(), GenerationTracerUtil.translateOutput(_outputNodes), templateNode.getReference());
      outputNodes.addAll(_outputNodes);
    }
    return outputNodes;
  }

  @NotNull
  private List<SNode> checkAdjacentFragments() throws TemplateProcessingFailureException {
    List<SNode> fragments = GeneratorUtilEx.getTemplateFragments(myTemplateNode);
    if (fragments.isEmpty()) {
      throw new TemplateProcessingFailureException(myTemplateNode, "couldn't process template: no template fragments found");
    }
    if (fragments.size() > 1) {
      // GeneratorUtilEx.getTemplateFragments() shall not return null
      Iterator<SNode> it = fragments.iterator();
      SNode fragmentParent = it.next().getParent();
      assert fragmentParent != null; // free-floating fragment would be odd
      final SNode commonParent = fragmentParent.getParent();
      final SContainmentLink role = fragmentParent.getContainmentLink();
      while (it.hasNext()) {
        fragmentParent = it.next().getParent();
        assert fragmentParent != null; // free-floating fragment would be odd
        // it's parent template that specifies context node and its role where these template fragments would get injected into,
        // thus we check there's no assumption context node is different for fragments, and that they do not assume they may end up in distinct roles.
        // Technically, provided ITemplateProcessor.apply() would yield something extra but SNode, we could answer with Pair(SContainmentLink,SNode)
        // and inject TF outcome into different roles (see https://youtrack.jetbrains.com/issue/MPS-23373). However, this would compromise COPY-SRC
        // idea (it's attached to a distinct role), and we'd need something more general, like <<apply-templates/>> to handle all children
        if (commonParent != fragmentParent.getParent() || !role.equals(fragmentParent.getContainmentLink())) {
          String msg = "Couldn't process template: all template fragments must reside in the same parent node. Roles: expected %s, met %s";
          throw new TemplateProcessingFailureException(myTemplateNode, String.format(msg, role, fragmentParent.getContainmentLink().getName()));
        }
      }
    }
    return fragments;
  }
}
