/*
 * Copyright 2003-2017 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;

import jetbrains.mps.generator.impl.RuleManager;
import jetbrains.mps.generator.impl.TemplateSwitchGraph;
import jetbrains.mps.generator.plan.CheckpointIdentity;
import jetbrains.mps.generator.runtime.TemplateMappingConfiguration;
import jetbrains.mps.generator.runtime.TemplateModel;
import jetbrains.mps.generator.runtime.TemplateModule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.model.SModel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * Is it a final breakdown of shall I treat list of TMC as a raw input and re-order them as appropriate?
 * evgeny, 1/12/12
 */
public interface ModelGenerationPlan {

  List<Step> getSteps();

  Collection<TemplateModule> getGenerators();

  // PROVISIONAL, for migration purposes here.
  // Seems to be bad design - if we allow external plans (with partial generation), then
  // we shall leave it up to caller to decide whether to report errors about unexpected languages in an output model
  // Besides, even if language is 'covered', nothing tells it is 'yet to get processed' one, not the one 'already processed'
  // (so/ output nodes in that language still make no sense). Perhaps, could be part of Step, and each Step knows
  // what languages are to come down the road?
  // Shall drop, but first need to replace with a mechanism that collects all languages that emerge during generation
  boolean coversLanguage(SLanguage language);

  /*
   * Give a chance for a plan implementation to accommodate to actual input being transformed. Plans are inherently read-only,
   * so to give an input-specific plan, one need to make a copy of a template and augment it.
   * <p/>
   * Invoked once per each processed {@linkplain GeneratorTask generation task} prior to any step.
   *
   * @return An instance of the plan modified according to languages present in the task, or {@code this} if this
   * plan doesn't depend on transformed input.
   */
//  @NotNull
//  default ModelGenerationPlan prepare(@NotNull GeneratorTask task) {
//    return this;
//  }

  interface Step {
    // e.g. to print MCs that take part, if Transform step populates objects rather than return list of MC
//    String describe();
  }

  final class Checkpoint implements Step {
    private final boolean mySynchOnly;
    private final CheckpointIdentity myIdentity;

    public Checkpoint(@NotNull CheckpointIdentity cpIdentity) {
      this(cpIdentity, false);
    }

    public Checkpoint(@NotNull CheckpointIdentity cpIdentity, boolean synchOnly) {
      mySynchOnly = synchOnly;
      myIdentity = cpIdentity;
    }

    public String getName() {
      // FIXME I'm not sure I shall keep this method at all. Is it describe(), merely to present GP to user?
      return myIdentity.getName();
    }

    public CheckpointIdentity getIdentity() {
      return myIdentity;
    }

    /**
     * @return {@code true} if model state at this checkpoint is to be saved into a storage, {@code false} means this checkpoint is for
     * synchronization with other models only.
     */
    public boolean isPersisted() {
      return !mySynchOnly;
    }
  }

  final class Transform implements Step {
    private final TemplateMappingConfiguration[] myMapCfg;

    public Transform(@NotNull Collection<TemplateMappingConfiguration> tmc) {
      myMapCfg = tmc.toArray(new TemplateMappingConfiguration[tmc.size()]);
    }

    @NotNull
    public List<TemplateMappingConfiguration> getTransformations() {
      return Arrays.asList(myMapCfg);
    }

    // Do I need this?
    public List<TemplateModel> getTemplateModels() {
      ArrayList<TemplateModel> rv = new ArrayList<TemplateModel>(myMapCfg.length);
      // generally, there are very few distinct template models per step, don't care about performance here
      for (TemplateMappingConfiguration mc : myMapCfg) {
        if (!rv.contains(mc.getModel())) {
          rv.add(mc.getModel());
        }
      }
      return rv;
    }

    // alternatively, why not to give control over rule/switch manager to the step?
    // i.e. to avoid extra iface to hold both TMC and nested plan.
    // XXX need to drop !TMC.isApplicable and report these

    RuleManager getRuleManager() {
      return null;
    }

    void fill(RuleManager ruleManager) {
    }

    TemplateSwitchGraph getSwitchGraph() {
      return null;
    }
  }


  /**
   * Marker to indicate source capable to supply ModelGenerationPlan for a model
   */
  interface Provider {
    /**
     * @param model what we need plan for.
     * @return {@code null} if this provider could not give a plan for the model
     */
    @Nullable
    ModelGenerationPlan getPlan(@NotNull SModel model);
  }
}
