/*
 * Copyright 2003-2015 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.reference;

import jetbrains.mps.generator.impl.GeneratorUtil;
import jetbrains.mps.generator.impl.TemplateGenerator;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.smodel.DynamicReference.DynamicReferenceOrigin;
import jetbrains.mps.util.SNodeOperations;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.model.SReference;

/**
 * Restore a reference originally between template nodes
 * Evgeny Gryaznov, 11/19/10
 */
public class ReferenceInfo_Template extends ReferenceInfo {
  private final SNodeReference myTemplateSourceNode;
  private final String myTemplateTargetNode;
  private final TemplateContext myContext;
  private final String myResolveInfo;


  public ReferenceInfo_Template(SNodeReference sourceNode, String targetNodeId, String resolveInfo, TemplateContext context) {
    myContext = context;
    myTemplateSourceNode = sourceNode;
    myTemplateTargetNode = targetNodeId;
    myResolveInfo = resolveInfo;
  }

  @Nullable
  @Override
  public SReference create(@NotNull PostponedReference ref) {
    SNode outputTargetNode = doResolve_Straightforward(ref);
    if (outputTargetNode != null) {
      return createStaticReference(ref, outputTargetNode);
    }
    if (myResolveInfo != null) {
      final SReference dr = createDynamicReference(ref, myResolveInfo, new DynamicReferenceOrigin(myTemplateSourceNode, myContext.getInput().getReference()));
      ref.getGenerator().registerDynamicReference(dr);
      return dr; 
    }
    return createInvalidReference(ref, null);
  }

  private SNode doResolve_Straightforward(PostponedReference ref) {
    final TemplateGenerator generator = ref.getGenerator();
    // try to find for the same inputNode
    SNode outputTargetNode = generator.findOutputNodeByInputAndTemplateNode(myContext.getInput(), myTemplateTargetNode);
    if (outputTargetNode != null) {
      checkCrossRootTemplateReference(outputTargetNode, ref);
      return outputTargetNode;
    }

    // if template has been applied exactly once, then we have unique output node for each template node
    outputTargetNode = generator.findOutputNodeByTemplateNodeUnique(myTemplateTargetNode);
    if (outputTargetNode != null) {
      checkCrossRootTemplateReference(outputTargetNode, ref);
      return outputTargetNode;
    }

    // try to find for indirect input nodes
    // XXX likely, myContext.getInput() we've checked already above, would come here again.
    for (SNode historyInputNode : myContext.getInputHistory()) {
      outputTargetNode = generator.findOutputNodeByInputAndTemplateNode(historyInputNode, myTemplateTargetNode);
      if (outputTargetNode != null) {
        checkCrossRootTemplateReference(outputTargetNode, ref);
        return outputTargetNode;
      }
    }
    return null;
  }

  private void checkCrossRootTemplateReference(@NotNull SNode outputTarget, PostponedReference ref) {
    final TemplateGenerator generator = ref.getGenerator();
    if (!generator.isStrict() || !generator.isIncremental()) {
      return;
    }

    // Additional check - reference target should be generated from the same root (required for incremental generation)
    // I believe origin of this error is the chance next incremental generation may proceed target as unchanged, and there would be no
    // output mapping for the given templateNodeId, and to prevent this case the error is reported.
    // There are, however, legitimate references like this when target is known to be regenerated regardless of incremental
    // mode (e.g. QueriesGenerated in generator) - target being a "common" dependency, the one that is affected by any model change.
    SNode outputTargetRoot = outputTarget.getContainingRoot();
    SNode outputSourceRoot = ref.getSourceNode().getContainingRoot();
    SModel model = outputTargetRoot.getModel();
    if (model == null || outputTargetRoot.getParent() != null) {
      SNode inputSourceRoot = generator.getOriginalRootByGenerated(outputSourceRoot);
      SNode inputTargetRoot = generator.getOriginalRootByGenerated(outputTargetRoot);
      if (inputTargetRoot != inputSourceRoot) {
        generator.getLogger().warning(myTemplateSourceNode, "references across templates for different roots are not allowed: use mapping labels or turn off incremental mode, " +
            "source root: " + (inputSourceRoot == null ? "<conditional root>" : SNodeOperations.getDebugText(inputSourceRoot)) +
            ", target root: " + (inputTargetRoot == null ? "<conditional root>" : SNodeOperations.getDebugText(inputTargetRoot)),
            GeneratorUtil.describeIfExists(ref.getSourceNode(), "source"),
            GeneratorUtil.describeIfExists(outputTarget, "target"));
      }
    }
  }
}
