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

import jetbrains.mps.generator.impl.GenerationFailureException;
import jetbrains.mps.generator.impl.TemplateQueryException;
import jetbrains.mps.generator.impl.query.CallArgumentQuery;
import jetbrains.mps.generator.impl.query.IfMacroCondition;
import jetbrains.mps.generator.impl.query.InlineSwitchCaseCondition;
import jetbrains.mps.generator.impl.query.InsertMacroQuery;
import jetbrains.mps.generator.impl.query.MapNodeQuery;
import jetbrains.mps.generator.impl.query.MapPostProcessor;
import jetbrains.mps.generator.impl.query.PropertyValueQuery;
import jetbrains.mps.generator.impl.query.ReferenceTargetQuery;
import jetbrains.mps.generator.impl.query.SourceNodeQuery;
import jetbrains.mps.generator.impl.query.SourceNodesQuery;
import jetbrains.mps.generator.impl.query.VariableValueQuery;
import jetbrains.mps.generator.runtime.GenerationException;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.generator.runtime.TemplateCreateRootRule;
import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment;
import jetbrains.mps.generator.runtime.TemplateMappingScript;
import jetbrains.mps.generator.runtime.TemplateReductionRule;
import jetbrains.mps.generator.runtime.TemplateRootMappingRule;
import jetbrains.mps.generator.runtime.TemplateRuleWithCondition;
import jetbrains.mps.generator.runtime.TemplateWeavingRule;
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 java.util.Collection;

/**
 * Default implementation that executes queries without any further activity.
 *
 * XXX Note, evaluate methods account for any trouble in user code, and wrap them with {@link TemplateQueryException}.
 * However, {@link jetbrains.mps.generator.impl.interpreted.ReflectiveQueryProvider} handles unexpected exceptions itself,
 * while generated templates don't use this QEC at all. The only case when these catch() work is non-reflective queries, the one
 * that we'd like to make primary (and, perhaps, only). See {@link jetbrains.mps.generator.impl.template.QueryExecutor} for
 * considerations whether we shall keep QE/QEC indirection, or get another provider that would wrap non-reflective queries with
 * try/catch and unexpected error handling (wrapping could be conditional). I lean towards a distinct provider as it gives
 * more flexibility (can mix different wrappers) and fine-grained control for wrappers like performance tracer.
 * Evgeny Gryaznov, Feb 10, 2010
 */
public class DefaultQueryExecutionContext implements QueryExecutionContext {

  private final ITemplateGenerator myGenerator;
  private final boolean myIsMultithread;

  public DefaultQueryExecutionContext(@NotNull ITemplateGenerator generator) {
    this(generator, true);
  }

  public DefaultQueryExecutionContext(@NotNull ITemplateGenerator generator, boolean isMultithread) {
    myGenerator = generator;
    myIsMultithread = isMultithread;
  }

  @Override
  public boolean evaluate(@NotNull InlineSwitchCaseCondition condition, @NotNull InlineSwitchCaseContext context) throws GenerationFailureException {
    try {
      return condition.check(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("condition of inline switch failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Override
  public boolean evaluate(@NotNull IfMacroCondition condition, @NotNull IfMacroContext context) throws GenerationFailureException {
    try {
    return condition.check(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("IF macro condition failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public SNode evaluate(@NotNull MapNodeQuery query, @NotNull MapSrcMacroContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("mapping function failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Override
  public void execute(@NotNull MapPostProcessor codeBlock, @NotNull MapSrcMacroPostProcContext context) throws GenerationFailureException {
    try {
      codeBlock.invoke(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("post-processing query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public Object evaluate(@NotNull PropertyValueQuery query, @NotNull PropertyMacroContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("failed to evaluate property value", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public SNode evaluate(@NotNull SourceNodeQuery query, @NotNull SourceSubstituteMacroNodeContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("source node query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public Object evaluate(@NotNull CallArgumentQuery query, @NotNull TemplateArgumentContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("Query for template call argument failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public Object evaluate(@NotNull VariableValueQuery query, @NotNull TemplateVarContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("VAR macro query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @NotNull
  @Override
  public Collection<SNode> evaluate(@NotNull SourceNodesQuery query, @NotNull SourceSubstituteMacroNodesContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("source nodes query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public SNode evaluate(@NotNull InsertMacroQuery query, @NotNull InsertMacroContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("INSERT macro query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Nullable
  @Override
  public Object evaluate(@NotNull ReferenceTargetQuery query, @NotNull ReferenceMacroContext context) throws GenerationFailureException {
    try {
      return query.evaluate(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("reference macro target query failed", t);
      ex.setQueryContext(context);
      throw ex;
    }
  }

  @Override
  public Collection<SNode> applyRule(TemplateReductionRule rule, TemplateContext context) throws GenerationException {
    try {
      return rule.apply(context);
    } catch (GenerationException ex) {
      throw ex;
    } catch (Throwable t) {
      GenerationFailureException ex = new GenerationFailureException("error applying reduction rule", t);
      ex.setTemplateContext(context);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public boolean isApplicable(@NotNull TemplateRuleWithCondition rule, @NotNull TemplateContext context) throws GenerationFailureException {
    try {
      return rule.isApplicable(context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("error executing rule condition", t);
      ex.setTemplateContext(context);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public Collection<SNode> applyRule(TemplateRootMappingRule rule, TemplateContext context) throws GenerationException {
    try {
      return rule.apply(context);
    } catch (GenerationException ex) {
      throw ex;
    } catch (Throwable t) {
      GenerationFailureException ex = new GenerationFailureException("unexpected exception when applying root rule", t);
      ex.setTemplateContext(context);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public Collection<SNode> applyRule(TemplateCreateRootRule rule, TemplateExecutionEnvironment environment) throws GenerationException {
    try {
      return rule.apply(environment);
    } catch (GenerationException ex) {
      throw ex;
    } catch (Throwable t) {
      GenerationFailureException ex = new GenerationFailureException("unexpected exception when applying create root rule", t);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public boolean applyRule(TemplateWeavingRule rule, TemplateContext context, SNode outputContextNode) throws GenerationException {
    try {
      return rule.apply(context.getEnvironment(), context, outputContextNode);
    } catch (GenerationException ex) {
      throw ex;
    } catch (Throwable t) {
      GenerationFailureException ex = new GenerationFailureException("unexpected exception when applying weaving rule", t);
      ex.setTemplateContext(context);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public SNode getContextNode(TemplateWeavingRule rule, TemplateContext context) throws GenerationFailureException {
    try {
      return rule.getContextNode(context.getEnvironment(), context);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException("cannot evaluate rule context query", t);
      ex.setTemplateContext(context);
      ex.setTemplateModelLocation(rule.getRuleNode());
      throw ex;
    }
  }

  @Override
  public void executeScript(TemplateMappingScript mappingScript, SModel model) throws GenerationFailureException {
    try {
      mappingScript.apply(model, myGenerator);
    } catch (GenerationFailureException ex) {
      throw ex;
    } catch (Throwable t) {
      TemplateQueryException ex = new TemplateQueryException(String.format("error executing script %s", mappingScript.getLongName()), t);
      ex.setTemplateModelLocation(mappingScript.getScriptNode());
      throw ex;
    }
  }

  @Override
  public boolean isMultithreaded() {
    return myIsMultithread;
  }
}
