package jetbrains.mps.make.script;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.logging.Logger;
import java.util.Set;
import jetbrains.mps.make.facet.IFacet;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import jetbrains.mps.make.facet.ITarget;
import java.util.List;
import jetbrains.mps.internal.make.runtime.script.ValidationError;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import jetbrains.mps.make.facet.FacetRegistry;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.internal.make.runtime.script.InvalidScript;
import java.util.Map;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.internal.make.runtime.script.TargetRange;
import jetbrains.mps.internal.make.runtime.script.Script;
import jetbrains.mps.internal.collections.runtime.IMapping;
import jetbrains.mps.internal.make.runtime.util.GraphAnalyzer;

@GeneratedClass(node = "r:9e5578e0-37f0-4c9b-a301-771bcb453678(jetbrains.mps.make.script)/1479818508463261244", model = "r:9e5578e0-37f0-4c9b-a301-771bcb453678(jetbrains.mps.make.script)")
public class ScriptBuilder {
  private static Logger LOG = Logger.getLogger(ScriptBuilder.class);
  private final Set<IFacet.Name> myRequestedFacets = SetSequence.fromSet(new HashSet<IFacet.Name>());
  private final Set<IFacet.Name> myExcludedFacets = SetSequence.fromSet(new HashSet<IFacet.Name>());
  private Set<ITarget.Name> requestedTargets = SetSequence.fromSet(new HashSet<ITarget.Name>());
  private ITarget.Name finalTarget;
  private ITarget.Name startingTarget;
  private List<ValidationError> errors = ListSequence.fromList(new ArrayList<ValidationError>());
  private final FacetRegistry myFacetRegistry;

  public ScriptBuilder(FacetRegistry facetRegistry) {
    myFacetRegistry = facetRegistry;
  }
  public ScriptBuilder withFacetName(IFacet.Name facetName) {
    SetSequence.fromSet(myRequestedFacets).addElement(facetName);
    SetSequence.fromSet(myExcludedFacets).removeElement(facetName);
    return this;
  }
  public ScriptBuilder withFacetNames(IFacet.Name... facetNames) {
    return withFacetNames(Sequence.fromArray(facetNames));
  }
  public ScriptBuilder withFacetNames(Iterable<IFacet.Name> facetNames) {
    SetSequence.fromSet(myRequestedFacets).addSequence(Sequence.fromIterable(facetNames));
    SetSequence.fromSet(myExcludedFacets).removeSequence(Sequence.fromIterable(facetNames));
    return this;
  }
  public ScriptBuilder withoutFacet(IFacet.Name... facetNames) {
    SetSequence.fromSet(myExcludedFacets).addSequence(Sequence.fromIterable(Sequence.fromArray(facetNames)));
    SetSequence.fromSet(myRequestedFacets).removeSequence(Sequence.fromIterable(Sequence.fromArray(facetNames)));
    return this;
  }
  public ScriptBuilder withAuxTarget(ITarget.Name targetName) {
    if (targetName == null) {
      throw new NullPointerException();
    }
    SetSequence.fromSet(requestedTargets).addElement(targetName);
    return this;
  }
  public ScriptBuilder withStartingTarget(ITarget.Name targetName) {
    if (targetName == null) {
      throw new NullPointerException();
    }
    SetSequence.fromSet(requestedTargets).addElement(targetName);
    this.startingTarget = targetName;
    return this;
  }
  public ScriptBuilder withFinalTarget(ITarget.Name targetName) {
    if (targetName == null) {
      throw new NullPointerException();
    }
    SetSequence.fromSet(requestedTargets).addElement(targetName);
    this.finalTarget = targetName;
    return this;
  }
  public IScript toScript() {
    if (SetSequence.fromSet(myRequestedFacets).isEmpty()) {
      // well, in fact this could be legitimate scenario, when there are no facets for a set of models/modules and the set could be ignored 
      // however, at the moment, with lang.core as a super-language of any other and make facets it provides by default, empty script is likely an error
      // we do handle 'no used languages' case (MPS-26995) in ModulesClusterizer at the moment.
      error("No facets requested for the script, nothing to make");
      return new InvalidScript(errors);
    }
    Map<IFacet.Name, IFacet> facetsView = collectFacets();
    if (ListSequence.fromList(errors).isNotEmpty()) {
      return new InvalidScript(errors);
    }
    final Map<IFacet.Name, FacetRefs> refs = MapSequence.fromMap(new HashMap<IFacet.Name, FacetRefs>());
    this.collectRefs(refs, facetsView);
    if (ListSequence.fromList(errors).isNotEmpty()) {
      return new InvalidScript(errors);
    }
    Iterable<IFacet.Name> sortedFacets = this.toposortByExtended(refs, facetsView);
    if (ListSequence.fromList(errors).isNotEmpty()) {
      return new InvalidScript(errors);
    }
    TargetRange tr = new TargetRange();
    this.collectTargets(sortedFacets, tr, facetsView);
    if (ListSequence.fromList(errors).isNotEmpty()) {
      return new InvalidScript(errors);
    }
    Script sc = new Script(tr, finalTarget, startingTarget);
    sc.setFacetRegistry(myFacetRegistry);
    return sc;
  }
  private Map<IFacet.Name, IFacet> collectFacets() {
    Map<IFacet.Name, IFacet> facetsView = MapSequence.fromMap(new HashMap<IFacet.Name, IFacet>());
    for (IFacet.Name fn : SetSequence.fromSet(myRequestedFacets)) {
      IFacet fct = myFacetRegistry.lookup(fn);
      if (fct != null) {
        MapSequence.fromMap(facetsView).put(fn, fct);
      } else {
        String msg = "facet not found: " + fn;
        error(msg);
      }
    }
    return facetsView;
  }
  private void collectTargets(Iterable<IFacet.Name> sortedFacets, TargetRange tr, final Map<IFacet.Name, IFacet> facetsView) {
    List<ITarget> allTargets = ListSequence.fromList(Sequence.fromIterable(sortedFacets).translate((fname) -> MapSequence.fromMap(facetsView).get(fname).targets()).toList()).reversedList();
    for (ITarget trg : ListSequence.fromList(allTargets)) {
      if (SetSequence.fromSet(requestedTargets).contains(trg.getName())) {
        tr.addTarget(trg);
      }
    }
    for (ITarget.Name tn : SetSequence.fromSet(requestedTargets)) {
      if (!(tr.hasTarget(tn))) {
        error("target not found: " + tn);
      }
    }
    if (ListSequence.fromList(errors).isNotEmpty()) {
      return;
    }
    tr.addRelatedPrecursors(Sequence.fromIterable(MapSequence.fromMap(facetsView).values()).translate((fct) -> fct.targets()));
  }
  private void collectRefs(final Map<IFacet.Name, FacetRefs> refs, Map<IFacet.Name, IFacet> facetsView) {
    for (IFacet fct : Sequence.fromIterable(MapSequence.fromMap(facetsView).values())) {
      FacetRefs facetRefs = new FacetRefs(facetsView);
      facetRefs.collect(fct);
      MapSequence.fromMap(refs).put(fct.getName(), facetRefs);
    }
  }
  private Iterable<IFacet.Name> toposortByExtended(final Map<IFacet.Name, FacetRefs> refs, Map<IFacet.Name, IFacet> facetsView) {
    for (IMapping<IFacet.Name, FacetRefs> m : SetSequence.fromSet(MapSequence.fromMap(refs).mappingsSet())) {
      IFacet fct = MapSequence.fromMap(facetsView).get(m.key());
      for (IFacet ex : ListSequence.fromList(m.value().extended)) {
        ListSequence.fromList(MapSequence.fromMap(refs).get(ex.getName()).extendedBy).addElement(fct);
      }
    }
    GraphAnalyzer<IFacet.Name> ga = new GraphAnalyzer<IFacet.Name>() {
      @Override
      public Iterable<IFacet.Name> forwardEdges(IFacet.Name v) {
        return ListSequence.fromList(MapSequence.fromMap(refs).get(v).extendedBy).select((f) -> f.getName());
      }
      @Override
      public Iterable<IFacet.Name> backwardEdges(IFacet.Name v) {
        return ListSequence.fromList(MapSequence.fromMap(refs).get(v).extended).select((f) -> f.getName());
      }
      @Override
      public Iterable<IFacet.Name> vertices() {
        return MapSequence.fromMap(refs).keySet();
      }
    };
    for (List<IFacet.Name> cyc : ListSequence.fromList(ga.findCycles())) {
      error("found cycle: " + cyc);
    }
    return ga.topologicalSort();
  }
  private void error(String message) {
    LOG.debug(message);
    ListSequence.fromList(this.errors).addElement(new ValidationError(this, message));
  }
  private class FacetRefs {
    /*package*/ List<IFacet> extended = ListSequence.fromList(new ArrayList<IFacet>());
    /*package*/ List<IFacet> extendedBy = ListSequence.fromList(new ArrayList<IFacet>());
    /*package*/ List<IFacet> required = ListSequence.fromList(new ArrayList<IFacet>());
    /*package*/ List<IFacet> optional = ListSequence.fromList(new ArrayList<IFacet>());
    private Map<IFacet.Name, IFacet> facetsView;
    public FacetRefs(Map<IFacet.Name, IFacet> facetsView) {
      this.facetsView = facetsView;
    }
    public void collect(IFacet fct) {
      for (IFacet.Name req : Sequence.fromIterable(fct.extended())) {
        addIfExists(fct, req, extended, true);
      }
      for (IFacet.Name req : Sequence.fromIterable(fct.required())) {
        addIfExists(fct, req, required, true);
      }
      for (IFacet.Name opt : Sequence.fromIterable(fct.optional())) {
        addIfExists(fct, opt, optional, false);
      }
    }
    private void addIfExists(IFacet requestor, IFacet.Name requestee, List<IFacet> destination, boolean required) {
      IFacet f = MapSequence.fromMap(facetsView).get(requestee);
      if (f != null) {
        ListSequence.fromList(destination).addElement(f);
      } else {
        reportMissing(requestor, requestee, required);
      }
    }
    private void reportMissing(IFacet requestor, IFacet.Name requestee, boolean required) {
      String reason = "";
      if (SetSequence.fromSet(myExcludedFacets).contains(requestee)) {
        reason = "; facet has been explicitly excluded from the script";
      }
      if (required) {
        error(String.format("facet %s requested by %s not found%s", requestee, requestor.getName(), reason));
      } else {
        LOG.debug(String.format("optional facet %s requested by %s not found, ignored%s", requestee, requestor.getName(), reason));
      }
    }
  }
}
