package jetbrains.mps.build.util;

/*Generated by MPS */

import java.util.Collection;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.build.behavior.BuildMacro__BehaviorDescriptor;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.generator.template.TemplateQueryContext;
import java.util.concurrent.ConcurrentMap;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SConcept;

public final class MacroHelper {
  private final Collection<SNode> availableMacros = new ArrayList<SNode>();
  private final Map<SNode, String> macroToName = new HashMap<SNode, String>();
  private final Map<SNode, String> exportNames = new HashMap<SNode, String>();
  private final Map<SNode, String> importNames = new HashMap<SNode, String>();
  private final Map<SNode, String> depPrefixes = new HashMap<SNode, String>();
  private final Set<String> usedNames = new HashSet<String>();
  private final Set<String> usedExports = new HashSet<String>();
  private final Set<String> usedPrefixes = new HashSet<String>();
  private final SNode project;
  private final MacroContext context;
  private MacroHelper(SNode project, MacroContext context) {
    this.project = project;
    this.context = context;
  }
  public void init() {
    for (SNode m : SLinkOperations.getChildren(project, LINKS.macros$r8_A)) {
      if (usedNames.contains(SPropertyOperations.getString(m, PROPS.name$MnvL))) {
        context.reportProblem("duplicate macro name", m);
      }
      add(m, null, ((boolean) BuildMacro__BehaviorDescriptor.isPublic_id5FtnUVJQZyL.invoke(m) ? SPropertyOperations.getString(project, PROPS.name$MnvL) + "." + SPropertyOperations.getString(m, PROPS.name$MnvL) : null));
    }
    for (SNode dep : SNodeOperations.ofConcept(SLinkOperations.getChildren(project, LINKS.dependencies$redY), CONCEPTS.BuildProjectDependency$sN)) {
      SNode depProject = SLinkOperations.getTarget(dep, LINKS.script$6Ehy);
      MacroHelper depHelper = context.getMacros(depProject);
      if (depHelper == null) {
        continue;
      }

      for (SNode m : depHelper.getAvailableMacros()) {
        if (macroToName.containsKey(m)) {
          continue;
        }

        String exportName = depHelper.getExportName(m);
        if (exportName == null) {
          continue;
        }
        String depprefix = depPrefixes.get(dep);
        if (depprefix == null) {
          depprefix = makeUnique("import." + SPropertyOperations.getString(SLinkOperations.getTarget(dep, LINKS.script$6Ehy), PROPS.name$MnvL), usedPrefixes);
          depPrefixes.put(dep, depprefix);
        }
        add(m, depprefix + "." + exportName, exportName);
      }
    }
  }
  private void add(SNode macro, String importName, String exportName) {
    SNode macroProject = SNodeOperations.as(SNodeOperations.getContainingRoot(macro), CONCEPTS.BuildProject$ae);
    if (macroProject == null) {
      context.reportProblem("macro is defined outside of the project", macro);
      return;
    }
    String name = makeUnique((macroProject == project ? SPropertyOperations.getString(macro, PROPS.name$MnvL) : SNodeOperations.present(macroProject) + "." + SPropertyOperations.getString(macro, PROPS.name$MnvL)), usedNames);
    macroToName.put(macro, name);
    availableMacros.add(macro);
    if (importName != null) {
      importNames.put(macro, importName);
    }
    if (exportName != null) {
      exportName = makeUnique(exportName, usedExports);
      exportNames.put(macro, exportName);
    }
  }
  private static String makeUnique(String baseName, Set<String> usedNames) {
    String name = baseName;
    int i = 1;
    while (!(usedNames.add(name))) {
      name = baseName + i++;
    }
    return name;
  }
  public Collection<SNode> getAvailableMacros() {
    return availableMacros;
  }
  public Iterable<SNode> getVarsContainers() {
    return Sequence.fromIterable(SNodeOperations.ofConcept(SLinkOperations.getChildren(this.project, LINKS.dependencies$redY), CONCEPTS.BuildProjectDependency$sN)).where((it) -> depPrefixes.containsKey(it));
  }
  public Iterable<SNode> getMacrosToExport() {
    return Sequence.fromIterable(((Iterable<SNode>) availableMacros)).where((it) -> exportNames.containsKey(it));
  }
  public Iterable<SNode> getMacrosToImport() {
    return Sequence.fromIterable(((Iterable<SNode>) availableMacros)).where((it) -> importNames.containsKey(it));
  }
  public String getName(SNode macro) {
    return macroToName.get(context.getOriginalMacro(macro));
  }
  public String getExportName(SNode macro) {
    return exportNames.get(context.getOriginalMacro(macro));
  }
  public String getImportName(SNode macro) {
    return importNames.get(context.getOriginalMacro(macro));
  }
  public String getPrefix(SNode dep) {
    return depPrefixes.get(context.getOriginalDep(dep));
  }
  public String getProjectName() {
    return SPropertyOperations.getString(project, PROPS.name$MnvL);
  }
  public void report(String message, SNode node) {
    context.reportProblem(message, node);
  }
  public static class MacroContext {
    private final Set<SNode> seenProjects = new HashSet<SNode>();
    private final TemplateQueryContext genContext;
    private final ConcurrentMap<SNode, MacroHelper> existingMacros;
    public MacroContext(SNode project, TemplateQueryContext genContext) {
      this.genContext = genContext;
      this.existingMacros = GenerationUtil.<SNode,MacroHelper>getSessionMap(project, genContext, "macroHelpers");
    }
    public MacroHelper getMacros(SNode dep) {
      dep = SNodeOperations.as(DependenciesHelper.getOriginalNode(dep, genContext), CONCEPTS.BuildProject$ae);
      if (dep == null) {
        return null;
      }
      if (seenProjects.add(dep)) {
        try {
          MacroHelper helper = this.existingMacros.get(dep);
          if (helper == null) {
            helper = new MacroHelper(dep, this);
            helper.init();
            if (this.existingMacros.putIfAbsent(dep, helper) != null) {
              helper = this.existingMacros.get(dep);
            }
          }
          return helper;
        } finally {
          seenProjects.remove(dep);
        }
      } else {
        reportProblem("cycle in project dependencies", dep);
        return null;
      }
    }
    public SNode getOriginalMacro(SNode macro) {
      return SNodeOperations.as(DependenciesHelper.getOriginalNode(macro, genContext), CONCEPTS.BuildMacro$qd);
    }
    public SNode getOriginalDep(SNode dep) {
      return SNodeOperations.as(DependenciesHelper.getOriginalNode(dep, genContext), CONCEPTS.BuildProjectDependency$sN);
    }
    public void reportProblem(String message, SNode node) {
      genContext.showErrorMessage(node, message);
    }
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink macros$r8_A = MetaAdapterFactory.getContainmentLink(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x4df58c6f18f84a13L, 0x4df58c6f18f84a22L, "macros");
    /*package*/ static final SContainmentLink dependencies$redY = MetaAdapterFactory.getContainmentLink(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x4df58c6f18f84a13L, 0x4df58c6f18f84a25L, "dependencies");
    /*package*/ static final SReferenceLink script$6Ehy = MetaAdapterFactory.getReferenceLink(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x454b730dd908c220L, 0x4df58c6f18f84a24L, "script");
  }

  private static final class PROPS {
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept BuildProjectDependency$sN = MetaAdapterFactory.getConcept(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x454b730dd908c220L, "jetbrains.mps.build.structure.BuildProjectDependency");
    /*package*/ static final SConcept BuildProject$ae = MetaAdapterFactory.getConcept(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x4df58c6f18f84a13L, "jetbrains.mps.build.structure.BuildProject");
    /*package*/ static final SConcept BuildMacro$qd = MetaAdapterFactory.getConcept(0x798100da4f0a421aL, 0xb99171f8c50ce5d2L, 0x4df58c6f18f84a1fL, "jetbrains.mps.build.structure.BuildMacro");
  }
}
